From 1f5df017a17f01a33e09a7671f91eb031f74f5d5 Mon Sep 17 00:00:00 2001 From: Dusty Mabe Date: Mon, 8 Dec 2025 11:39:30 -0500 Subject: [PATCH] hscontrol: log acme/autocert errors (#2933) --- hscontrol/app.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/hscontrol/app.go b/hscontrol/app.go index 4ce98719..a144ebd7 100644 --- a/hscontrol/app.go +++ b/hscontrol/app.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "errors" "fmt" + "io" "net" "net/http" _ "net/http/pprof" // nolint @@ -877,6 +878,11 @@ func (h *Headscale) getTLSSettings() (*tls.Config, error) { Cache: autocert.DirCache(h.cfg.TLS.LetsEncrypt.CacheDir), Client: &acme.Client{ DirectoryURL: h.cfg.ACMEURL, + HTTPClient: &http.Client{ + Transport: &acmeLogger{ + rt: http.DefaultTransport, + }, + }, }, Email: h.cfg.ACMEEmail, } @@ -997,3 +1003,28 @@ func readOrCreatePrivateKey(path string) (*key.MachinePrivate, error) { func (h *Headscale) Change(cs ...change.ChangeSet) { h.mapBatcher.AddWork(cs...) } + +// Provide some middleware that can inspect the ACME/autocert https calls +// and log when things are failing. +type acmeLogger struct { + rt http.RoundTripper +} + +// RoundTrip will log when ACME/autocert failures happen either when err != nil OR +// when http status codes indicate a failure has occurred. +func (l *acmeLogger) RoundTrip(req *http.Request) (*http.Response, error) { + resp, err := l.rt.RoundTrip(req) + if err != nil { + log.Error().Err(err).Str("url", req.URL.String()).Msg("ACME request failed") + return nil, err + } + + if resp.StatusCode >= http.StatusBadRequest { + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + log.Error().Int("status_code", resp.StatusCode).Str("url", req.URL.String()).Bytes("body", body).Msg("ACME request returned error") + } + + return resp, nil +}