Even when returning, the data must go through the proxy server, as it's the server that holds the TCP connection of the client, and is supposed to send the answer through it.
Solution without proxy server working on the back path do exist, but those are much harder to implement correctly (dirty TCPIP/iptables tricks are usually involved).
EDIT: good solution without proxy server:
"Clean solution" that would magically forward TCP SYNs and would offload everything else from the load balancer is probably a dream, and I'm not aware of any such thing.
Instead, there are two commonly used approaches:
- DNS load balancing: have more servers on multiple publicly accessible IP addresses, specify more A entries for your website, possibly make your DNS server shuffle the answer to add randomness. Works perfectly on many commercial installations. Pros: simple, Cons: "breakable", and you need public IP space.
- Firewall-only load balancing: There's most probably a single router that handles all your traffic anyway, so adding a few firewall rules on it usually doesn't harm actual performance. The thing you're looking for (on linux) is using the iptables' DNAT target, and some kind of "random" or "hashing" match. I personally use something like this on several installations:
iptables -t nat -A PREROUTING -d 184.108.40.206 -p tcp --dport 80 -m random --average 50 -j DNAT --to 192.168.0.2
iptables -t nat -A PREROUTING -d 220.127.116.11 -p tcp --dport 80 -j DNAT --to 192.168.0.3
Replace 18.104.22.168 with your public IP address and 192.168.x.x with your servers. Don't forget to modify --average values.
Pros: whole solution looks like a single server from outside, loadbalancing is unbreakable by DoSing a single server, etc.
Cons: you want to have the router and all the servers at one spot.