Start your 3-day free trial
Sign up to experience all premium features at no cost.
*Available only to new users. Each user is limited to one trial.


VPS relay port forwarding adds one hop in front of a proxy server you already run: clients connect to a relay VPS, and the relay passes their TCP and UDP packets to the landing server without decrypting them. You can do it in the kernel with nftables DNAT plus source NAT, or in user space with a GOST forwarder; both work only between servers you own or are authorized to use.
Key Takeaways
- A relay changes the entry address, not the exit: websites still see the landing server.
- nftables DNAT needs IP forwarding and a matching SNAT or masquerade rule, or replies take the wrong path.
- GOST forwards at the application layer and needs no kernel forwarding, but UDP needs
keepAlivefor long sessions.- Both methods make the landing server see the relay's address instead of each client's.
- Keep the client's TLS, REALITY, or key settings unchanged; only the server address and port move.
This procedure is based on the official sources listed below; a complete end-to-end test has not been performed. The commands assume Ubuntu 24.04 LTS on both servers, nftables from the distribution, and GOST v3.3.0; they describe documented behavior, not measured results. Start with the operator duties for self-hosted servers so you know who controls each server.
A relay is useful when clients reach one data center more reliably than another, or when you want to change the public entry address without rebuilding the proxy itself. It is not a second layer of encryption and does not hide traffic from the landing server; that is a different design, closer to a multi-hop chain where each hop runs its own protocol.
Forwarding a port also publishes it. Read how inbound port forwarding exposes a service before opening anything, and decide which source addresses should be allowed. A relay cannot fix a landing server that is already slow or overloaded: measure that first with the slow self-hosted server checks. If maintaining two servers, two firewalls, and two upgrade schedules is more work than your task needs, weigh that workload against a managed VPN before you add the hop.
Use only servers you administer, with console access that does not depend on the forwarded port and SSH host keys verified out of band. Avoid one-click relay scripts from unknown sources: they often replace firewall rules wholesale and leave you without a record of what changed.
| Question | nftables DNAT relay | GOST port forwarding |
|---|---|---|
| Where it runs | Linux kernel netfilter with connection tracking | A user-space process listening on the relay |
Needs ip_forward | Yes | No |
| Source seen by landing | Relay address, after SNAT or masquerade | Relay address, because GOST opens its own connection |
| UDP handling | Per-flow conntrack entries | UDP listener with keepAlive and ttl options |
| Extra features | Counters, matching, no extra daemon | Multiple targets, selection strategy, optional chain hops |
| Main failure mode | Missing SNAT or a forward-policy drop | Process stopped or UDP channel closed early |
Stateful NAT in nftables sets up a binding on the first packet of a flow, and later packets reuse it.[1] GOST's TCP and UDP forwarding services accept a connection on the relay and open a new one to each target node, and they can also send traffic through a chain hop with -F, which requires a matching service on the next server.[2] This guide forwards plainly with either method; pick one per port.
In the examples, the relay's public interface is eth0 with address 198.51.100.10, and the landing server is 203.0.113.30, where an existing proxy listens on TCP and UDP 443. Replace all of these with your real values and confirm the landing service works directly before adding the relay.
Pick relay ports that nothing else uses: here 8443 for nftables and 8444 for GOST, so you can compare the two. Check with sudo ss -lntup on the relay. Save a copy of the relay's current rules with sudo nft list ruleset > ~/relay-ruleset-before.nft and note any firewall manager in use, such as UFW or Docker.
If the landing server's firewall allows only known client addresses, add the relay's address for TCP and UDP 443. After the relay is in place, the landing server will see 198.51.100.10 as the peer for every relayed client, so per-client address limits and logs stop distinguishing users.
Kernel forwarding is off by default (net.ipv4.ip_forward is 0).[3] Create /etc/sysctl.d/90-relay-forward.conf containing net.ipv4.ip_forward = 1, then run sudo sysctl --system and confirm with sysctl net.ipv4.ip_forward. The kernel documentation notes that changing this value resets per-interface configuration parameters to their defaults, so re-check any custom interface settings afterward.[3]
Save these rules as /etc/nftables-relay/relay-nat.nft:
table ip relay_nat
delete table ip relay_nat
table ip relay_nat {
chain prerouting {
type nat hook prerouting priority -100; policy accept;
iifname "eth0" tcp dport 8443 counter dnat to 203.0.113.30:443
iifname "eth0" udp dport 8443 counter dnat to 203.0.113.30:443
}
chain postrouting {
type nat hook postrouting priority 100; policy accept;
ip daddr 203.0.113.30 tcp dport 443 counter masquerade
ip daddr 203.0.113.30 udp dport 443 counter masquerade
}
}
The first two lines create and then remove the table, so loading the file again replaces only relay_nat instead of touching other tables. Do not add flush ruleset: it would also remove rules owned by UFW, Docker, or your provider's agent. The prerouting chain rewrites the destination; the postrouting chain rewrites the source to the relay, so the landing server replies to the relay and conntrack translates the reply back to the client.[1] Without that SNAT step, replies leave the landing server directly toward the client and do not match the client's connection.
Check the syntax with sudo nft -c -f /etc/nftables-relay/relay-nat.nft, then load it with sudo nft -f and the same path.
A NAT rule does not override a filter. In nftables, an accept verdict in one base chain is not final, and a drop in another base chain on the same hook still takes effect.[4] Run sudo nft list ruleset | grep -B2 -A4 'hook forward'. If a forward chain has policy drop, add the allowance in the tool that owns that chain. With UFW, for example, use sudo ufw route allow proto tcp to 203.0.113.30 port 443 and the same rule with proto udp, instead of creating a competing table.
Download the Linux build from the official GOST v3.3.0 release, check its release notes and published checksums, and install it as /opt/gost/v3.3.0/gost, root-owned, mode 0755.[5] GOST reads a YAML or JSON file passed with -C.[6] Save this as /etc/gost-relay/gost.yml:
services:
- name: relay-tcp
addr: :8444
handler:
type: tcp
listener:
type: tcp
forwarder:
nodes:
- name: landing-tcp
addr: 203.0.113.30:443
- name: relay-udp
addr: :8444
handler:
type: udp
listener:
type: udp
metadata:
keepAlive: true
ttl: 30s
forwarder:
nodes:
- name: landing-udp
addr: 203.0.113.30:443
GOST's UDP listener closes a channel right after the response when keepAlive is false, which suits single DNS queries but breaks QUIC-based proxies that exchange many packets. With keepAlive: true, idle channels are checked every ttl and closed after at most two idle cycles.[2] The default readBufferSize is 4096 bytes, and larger UDP packets are truncated, so keep the default unless your protocol sends bigger datagrams.
Create a non-login account gost-relay and a systemd unit that runs /opt/gost/v3.3.0/gost -C /etc/gost-relay/gost.yml as that user, with Restart=on-failure and NoNewPrivileges=true. Port 8444 is above 1024, so the process needs no extra capability. Allow TCP and UDP 8444 in the relay's input firewall; GOST traffic is local to the relay, so no forward rule is required.
Copy the client's working profile and change only the server address to 198.51.100.10 and the port to 8443 (nftables) or 8444 (GOST). Leave the TLS server name, REALITY settings, UUID, or keys exactly as they were. Neither relay method terminates TLS, so the client still validates the landing server's certificate or REALITY parameters end to end.
If the original profile used the landing server's IP address as its TLS name, it will keep working through the relay because the name check is about the certificate, not the socket address. Keep the direct profile as a fallback until the checks below pass.
| Check method | Expected behavior | If it differs |
|---|---|---|
| Send a request through the relayed profile to an endpoint you control | Expected response | Test the direct profile first; then check relay listeners and rules |
sudo nft list table ip relay_nat on the relay | Counters on the DNAT and masquerade rules increase | No DNAT count: wrong interface or port. DNAT without masquerade count: check ip daddr |
sudo ss -tn state established '( sport = :443 )' on the landing server | Relay address appears as the peer | Client address appears: SNAT is missing or traffic bypassed the relay |
| Run a UDP-based workload through the relayed profile | UDP counters increase and sessions stay up | For GOST, confirm keepAlive: true; for nftables, check the UDP rules |
Stop only gost-relay, then retry port 8444 | New connections fail; port 8443 is unaffected | Another process owns 8444 or the client used the wrong profile |
| Direct profile to the landing server | Works, or is refused if you limited the landing to the relay | Adjust the landing firewall to match your plan |
Open the IP address tool through the relayed profile. It should show the landing server's egress address, not the relay's, because the relay only carries the inbound leg. If you later build a chain where each hop runs its own proxy protocol, verify it hop by hop with the Xray proxy chain checks instead of this table.
Make the nftables file load at boot with a small root-owned oneshot unit, relay-nat.service, that runs /usr/sbin/nft -f /etc/nftables-relay/relay-nat.nft after network-online.target, sets RemainAfterExit=yes, and uses ExecStop=/usr/sbin/nft delete table ip relay_nat. Enable it, reboot during a maintenance window, and repeat the counter and peer checks. The sysctl file already persists forwarding. Enable gost-relay only if you keep the GOST method.
Rollback starts with the client: switch back to the direct profile and confirm a fresh request. On the relay, run sudo systemctl disable --now relay-nat gost-relay, remove the UFW route or input rules you added, and delete /etc/sysctl.d/90-relay-forward.conf if nothing else needs forwarding, then run sudo sysctl --system. Compare sudo nft list ruleset with your saved copy. On the landing server, remove the relay's allow rule last.
No. Both methods forward packets or streams without decrypting them. Encryption still comes from the proxy protocol between your client and the landing server.
The landing server replies to the original client address instead of the relay. Add SNAT or masquerade so replies return through the relay's connection tracking.
Yes, on different ports, as in this guide. Do not point both at the same relay port, because the DNAT rule would capture packets before GOST sees them.
Not with these setups. Both make the landing server see the relay's address, so client-based limits and logs on the landing server lose that detail.
It can be, if the UDP listener keeps channels open. Set keepAlive to true and a sensible ttl, then verify that long sessions stay connected through the relay.
Not by itself. It can change the route clients take to reach your entry, but bandwidth, CPU, and congestion on the landing server still limit throughput.
Delete only the relay_nat table, the GOST service, and the specific allow rules you added. Avoid flushing the whole ruleset, which also removes rules owned by other tools.
Disclaimer: This article is for general informational purposes only and does not constitute legal, technical, or other professional advice. We make no guarantees regarding the accuracy, completeness, or timeliness of the content.
Sources:
Sources checked 14 October 2026.
Related Articles:
Sign up to experience all premium features at no cost.
*Available only to new users. Each user is limited to one trial.





