VPS Relay Port Forwarding: nftables DNAT or GOST

VPS Relay Port Forwarding: nftables DNAT or GOST

Jason Chen
October 14, 2026· 12 min read

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 keepAlive for 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.

When does VPS relay port forwarding help, and when does it not?

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.

nftables DNAT or GOST: which relay method should you use?

Questionnftables DNAT relayGOST port forwarding
Where it runsLinux kernel netfilter with connection trackingA user-space process listening on the relay
Needs ip_forwardYesNo
Source seen by landingRelay address, after SNAT or masqueradeRelay address, because GOST opens its own connection
UDP handlingPer-flow conntrack entriesUDP listener with keepAlive and ttl options
Extra featuresCounters, matching, no extra daemonMultiple targets, selection strategy, optional chain hops
Main failure modeMissing SNAT or a forward-policy dropProcess 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.

Step 1: Record the landing server and plan ports

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.

Step 2: Enable forwarding and add nftables DNAT with SNAT

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.

Allow the forwarded traffic through the filter

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.

Step 3: Or forward the same service with GOST

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.

Step 4: Point the client at the relay

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.

How do you verify the relay path and return traffic?

Check methodExpected behaviorIf it differs
Send a request through the relayed profile to an endpoint you controlExpected responseTest the direct profile first; then check relay listeners and rules
sudo nft list table ip relay_nat on the relayCounters on the DNAT and masquerade rules increaseNo DNAT count: wrong interface or port. DNAT without masquerade count: check ip daddr
sudo ss -tn state established '( sport = :443 )' on the landing serverRelay address appears as the peerClient address appears: SNAT is missing or traffic bypassed the relay
Run a UDP-based workload through the relayed profileUDP counters increase and sessions stay upFor GOST, confirm keepAlive: true; for nftables, check the UDP rules
Stop only gost-relay, then retry port 8444New connections fail; port 8443 is unaffectedAnother process owns 8444 or the client used the wrong profile
Direct profile to the landing serverWorks, or is refused if you limited the landing to the relayAdjust 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.

How do you persist, roll back, and clean up?

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.

Summary

  • A relay moves the entry point only; the landing server remains the exit and still decrypts nothing new.
  • nftables needs forwarding, DNAT, SNAT, and a forward-filter allowance that matches your firewall manager.
  • GOST needs no kernel forwarding but must keep UDP channels alive for long sessions.
  • Verify counters, landing-side peers, UDP, and exit address before retiring the direct profile.

FAQ

Does a VPS relay add encryption to my proxy traffic?

No. Both methods forward packets or streams without decrypting them. Encryption still comes from the proxy protocol between your client and the landing server.

Why do connections hang when only DNAT is configured?

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.

Can I use GOST and nftables on the same relay?

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.

Will the landing server still see my real client address?

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.

Is GOST UDP forwarding suitable for QUIC-based proxies?

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.

Does a relay make a slow landing server faster?

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.

How do I remove the relay without breaking other firewall rules?

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:

  1. nftables wiki: Performing Network Address Translation (NAT) — https://wiki.nftables.org/wiki-nftables/index.php/Performing_Network_Address_Translation_(NAT)
  2. GOST: Port forwarding — https://gost.run/en/tutorials/port-forwarding/
  3. Linux kernel documentation: IP sysctl — https://docs.kernel.org/networking/ip-sysctl.html
  4. nftables wiki: Configuring chains — https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains
  5. GOST release v3.3.0 — https://github.com/go-gost/gost/releases/tag/v3.3.0
  6. GOST: Command line reference — https://gost.run/en/reference/configuration/cmd/

Sources checked 14 October 2026.

Related Articles:

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: nftables DNAT or GOST | AethoVPN