Railway HTTPS strips certain upgrade headers, like Headscale
therobotcarlson
PROOP

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.

Awaiting User Response

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


Welcome!

Sign in to your Railway account to join the conversation.

Loading...