juanfont/headscale · error
MOCKOIDC_CLIENT_SECRET not defined
Error message
MOCKOIDC_CLIENT_SECRET not defined
What it means
Returned as HTTP 500 by the TS2021 noise upgrade handler when the initial /ts2021 request carries no 'Upgrade' header. The noise protocol rides on a WebSocket-style HTTP upgrade, so a missing Upgrade header means something in front of headscale terminated or plain-proxied the connection instead of passing the upgrade through. The server logs a specific warning about reverse proxy misconfiguration at the same moment.
Source
Thrown at cmd/headscale/cli/mockoidc.go:22
"context"
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"os"
"strconv"
"time"
"github.com/juanfont/headscale/hscontrol/util/zlog/zf"
"github.com/oauth2-proxy/mockoidc"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)
var (
errMockOidcClientIDNotDefined = errors.New("MOCKOIDC_CLIENT_ID not defined")
errMockOidcClientSecretNotDefined = errors.New("MOCKOIDC_CLIENT_SECRET not defined")
errMockOidcPortNotDefined = errors.New("MOCKOIDC_PORT not defined")
errMockOidcUsersNotDefined = errors.New("MOCKOIDC_USERS not defined")
)
const refreshTTL = 60 * time.Minute
var accessTTL = 2 * time.Minute
func init() {
rootCmd.AddCommand(mockOidcCmd)
}
var mockOidcCmd = &cobra.Command{
Use: "mockoidc",
Short: "Runs a mock OIDC server for testing",
Long: "This internal command runs a OpenID Connect for testing purposes",
RunE: func(cmd *cobra.Command, args []string) error {
err := mockOIDC()View on GitHub (pinned to 565fd254d0)
Solutions
- Fix the reverse proxy to pass WebSockets: nginx needs 'proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";' and HTTP/1.1 ('proxy_http_version 1.1;'); caddy and traefik pass upgrades by default when not terminating them.
- Check the concurrent warning log 'no upgrade header in TS2021 request...' to confirm this exact path.
- Bypass the proxy for /ts2021 (or expose headscale directly on its own port) to verify tailscaled registers fine, then re-enable proxying with correct headers.
- Ensure no intermediary (CDN, ALB, mTLS terminator) downgrades or strips hop-by-hop headers.
Example fix
# before (nginx strips upgrade -> 500 Internal error on /ts2021)
location / {
proxy_pass http://headscale;
}
# after
location / {
proxy_pass http://headscale;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
} Defensive patterns
Strategy: validation
Validate before calling
// Smoke-test that the upgrade headers survive your proxy chain before pointing tailscaled at it.
req, _ := http.NewRequest(http.MethodGet, serverURL+"/ts2021", nil)
req.Header.Set("Upgrade", "websocket")
req.Header.Set("Connection", "Upgrade")
resp, err := client.Do(req)
if err != nil || (resp != nil && resp.StatusCode != http.StatusSwitchingProtocols && resp.StatusCode != http.StatusBadRequest) {
// a 500 here means Upgrade was stripped — fix the proxy before registering nodes
log.Printf("upgrade header not passed through: status=%v err=%v", resp, err)
} Prevention
- Configure WebSocket pass-through (Upgrade/Connection headers, HTTP/1.1) on any proxy in front of headscale.
- Test the /ts2021 endpoint with an Upgrade header right after proxy changes.
- Watch for the 'no upgrade header in TS2021 request' warning in headscale logs as an early detector.
When it happens
Trigger: Pointing tailscaled at a URL served by a reverse proxy that does not forward Upgrade/Connection headers (nginx without proxy_set_header Upgrade $http_upgrade; Connection $connection_upgrade); hitting the noise endpoint with a plain HTTP client (curl without upgrade headers); an HTTP/1.0-ish intermediary stripping hop-by-hop headers.
Common situations: New nginx/caddy/traefik/ingress-nginx config in front of headscale where WebSocket pass-through was not enabled; CDN or load balancer (e.g. plain ALB, Cloudflare free tier without WS) stripping upgrade headers; tailscaled --login-server pointed at a proxy hostname instead of headscale directly.
Related errors
- health check timed out
- MOCKOIDC_PORT not defined
- registering node: %w
- upgrading noise connection: %w
- command aborted by user
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/a2664d5597fd9fdb.
Report an issue: GitHub.