7 hours ago
The same request reaches the same echo server with different headers depending on whether it uses Railway HTTPS ingress or a raw TCP proxy.
Minimal server — Python standard library only:
import json
import os
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
body = json.dumps({
"Connection": self.headers.get("Connection"),
"Upgrade": self.headers.get("Upgrade"),
}).encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
HTTPServer(("0.0.0.0", int(os.getenv("PORT", "8080"))), Handler).serve_forever()
Expose port 8080 through both a Railway HTTPS domain and a TCP proxy, then run:
curl --http1.1 --max-time 15 -X POST \
-H 'Connection: Upgrade' \
-H 'Upgrade: tailscale-control-protocol' \
https://YOUR-SERVICE.up.railway.app/ts2021
Actual through Railway HTTPS:
{"Connection": null, "Upgrade": null}
Repeat against http://YOUR-TCP-PROXY:PORT/ts2021.
Expected—and actual through raw TCP:
{"Connection": "Upgrade", "Upgrade": "tailscale-control-protocol"}
Using Upgrade: websocket instead preserves both headers through HTTPS.
This blocks native Tailscale clients connecting to Headscale.
1 Replies
Status changed to Awaiting Railway Response Railway • about 7 hours ago
7 hours ago
Solved in Discord, but providing context here.
Both the Tailscale and Headscale servers perform an upgrade via an HTTP POST request, which isn't standard under the HTTP spec and isn't something we can support.
Apologies for the less-than-satisfactory outcome here.
Status changed to Awaiting User Response Railway • about 7 hours ago