The classic way of doing this with iptables DNAT and enabling IPv4 forwarding with syctl seems to work. Google's documentation also suggests this.

However, all traffic arriving at the backend VM appears to be originating from the head node (source IP replaced with local IP of the head node). Needless to say this breaks a lot of things, because the backend VM has no idea where a connection originates from (e.g. for identification and logging purposes).

Rule forwarding port 8000 to the backend:

iptables -t nat -A PREROUTING -p tcp -i eth0 -d <1-local> --dport 8000 -j DNAT --to-destination <2-local>

Rule for outgoing traffic back to head node:

iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

Legend: <1-local> = local IP of head, <2-local> = local IP of backend VM

Note that a route was added to the network for outgoing traffic according to the Google documentation (works fine). I understand there's a very comprehensive Port Forwarding feature under Load Balancing, but to be honest, I have no idea how to use that for a simple case like this.

How would I make sure the original source IPs are preserved? Am I missing something, of is this a "feature" of GCE sub-networks? 

3 Answers 3


You can use the HTTP Load Balancer with a Target proxy and preserve the client IP from the X-Forwarded field in the request header. More information about it can be found in the Help Center article.

  • Thanks, but HTTP won't work for me, because the protocols being used are based on TCP.
    – Anders
    Nov 15, 2016 at 14:18
  • @Anders, are you using the TCP LB?
    – George
    Nov 16, 2016 at 20:54
  • I tried so but it didn't work. Also a LB requires another dedicated public IP, which I wanted to avoid (@George)
    – Anders
    Nov 25, 2016 at 22:25

Finally I found a solution and tested it successfully in a production network.

The challenge with port forwarding in GCE is that instances only have a single network interface, so packets forwarded with DNAT get their source IP replaced by SNAT immediately when they run through the POSTROUTING chain. As this happens before they reach their destination instance, it looks like DNAT didn't work. The fix now is to protect forwarded packets after DNAT from immediate SNAT by skipping that rule in POSTROUTING. The second rule below does exactly that:

iptables -t nat -A PREROUTING -i eth0 -p tcp -d <1-local> --dport 8000 -j DNAT --to-destination <2-local>
iptables -t nat -A POSTROUTING -o eth0 -p tcp -d <2-local> --dport 8000 -j RETURN

The above rules can be repeated for any number of ports to be forwarded. The final rule below ensures all instances in the local network have Internet access w/o them having a public IP (this rule replaces the MASQUERADE rule suggested in the GCE documentation):

iptables -t nat -A POSTROUTING -o eth0 -j SNAT --to-source <1-local>

The network's gateway replaces <1-local> with the public IP before packets are sent back to the Internet.

Note that a GCE firewall rule must be added to allow all forwarded ports from anywhere, on both the head (bastion) and the backend instance.

This solution enables port forwarding to backend instances w/o a public IP, while those instances also have full access to the Internet. The latter also requires IP Forwarding to be enabled for the head node, and a default route added to the subnetwork, as explained here: https://cloud.google.com/compute/docs/networking in section "Configure a NAT gateway".


The approach I would take in your case is to use UDP or TCP load balancing. If you do not already have a suitable public IP in the same region as the VM(s) you need to allocate a new IP for that purpose.

You can create multiple load balancers using a single public IP address as long as those load balancers use different port numbers.

A UDP or TCP load balancer with only a single backend and no health checking is quite similar to a traditional port forwarding.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .