This one line of code fixed it for me:
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Expect:']);
You obviously need to add the empty Expect:
header to whatever other headers you're sending up, but that header is what fixes cURL for use with Google HTTP load balancers.
More Info
The Google document Setting Up HTTP(S) Load Balancing has a note near the bottom in the Notes and Restrictions section saying that HTTP/1.1 100 Continue
responses are not supported.
It seems that by default cURL will always set Expect: 100-continue
headers when you send a POST request. Thus apparently cURL is unable to send POST through a GCE HTTP load balancer by default.
On the end-user side, you just see 502 responses coming back from Google, which is all the more confusing since making the exact same POST to a server that is not behind the load balancer works perfectly fine.
However, presence of Expect: 100-continue
causes the Google Load Balancer to freak out and break the request.
On the server side the POST data cannot be parsed (it does not even arrive to the server, though the Content-Length
is reported correctly). In my case this caused the server to return a 500 Internal Server Error, which the GCE LB munges and sends back to the user as a 502 Bad Gateway error.
After adding an empty Expect:
header, my POST data is making it to my load balanced VMs correctly, they're parsing and returning valid responses, and my client is getting a 200 instead of a 502.
Thanks to this question which helped shed some light on the issue.