Skip to content

Tailscale serve implementation#3300

Open
B00M3000 wants to merge 7 commits into
juanfont:mainfrom
B00M3000:main
Open

Tailscale serve implementation#3300
B00M3000 wants to merge 7 commits into
juanfont:mainfrom
B00M3000:main

Conversation

@B00M3000

@B00M3000 B00M3000 commented Jun 7, 2026

Copy link
Copy Markdown
  • have read the CONTRIBUTING.md file
  • raised a GitHub issue or discussed it on the projects chat beforehand
  • added unit tests
  • added integration tests
  • updated documentation if needed
  • updated CHANGELOG.md

I recognize that this is majority vibe coded and I am not sure if it works. However, I thought that I should open a pull request as a draft for others to look at and work on. I hope that this is helpful in pushing the implementation of HTTPS on serve.

claude and others added 5 commits June 3, 2026 19:07
Adds ACMEChallengeServer, a minimal authoritative DNS server for the configured base_domain that answers the _acme-challenge TXT lookups Tailscale clients use to complete ACME DNS-01 challenges (tailscale cert / tailscale serve --https). It serves only the zone apex SOA/NS and the published challenge TXT records; node A/AAAA records stay internal to the tailnet via MagicDNS.

A new dns.https_certs config block (enabled, listen_addr, nameserver) gates the feature and is validated to require MagicDNS and a base_domain.

https://claude.ai/code/session_01LYishR5SuV14gARRS3LjZU
mapper advertises the node's FQDN in DNSConfig.CertDomains when dns.https_certs is enabled, which is the signal the client needs to allow tailscale cert / serve --https.

noise implements /machine/set-dns: a node may publish only the ACME challenge record for its own FQDN, which is then served by the authoritative DNS server. getAndValidateNode now takes a node key so both the map and set-dns handlers can share it.

app starts and stops the ACMEChallengeServer with the server lifecycle.

https://claude.ai/code/session_01LYishR5SuV14gARRS3LjZU
Adds a reference page and config example for per-node HTTPS certificates, marks Serve as supported in the features matrix, and records the change in the changelog.

Adds an integration test asserting CertDomains is advertised to a connected client. The full ACME issuance flow is not CI-testable because it requires a publicly delegated base_domain reachable by Let's Encrypt; the DNS server and set-dns handler are covered by unit tests instead.

https://claude.ai/code/session_01LYishR5SuV14gARRS3LjZU
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 7, 2026 01:32

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds support for per-node HTTPS/TLS certificate provisioning to enable tailscale serve --https / tailscale cert under Headscale by advertising DNSConfig.CertDomains, implementing the /machine/set-dns endpoint, and running an embedded authoritative DNS server for ACME DNS-01 challenges.

Changes:

  • Introduces dns.https_certs configuration and validation (requires MagicDNS + base_domain).
  • Implements ACME DNS-01 challenge handling: /machine/set-dns + embedded authoritative DNS server.
  • Adds docs, changelog entry, and unit/integration tests for CertDomains and DNS challenge server behavior.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
mkdocs.yml Adds Serve HTTPS cert docs to site navigation.
integration/serve_test.go Integration coverage for advertising DNSConfig.CertDomains.
hscontrol/types/config.go Adds dns.https_certs config + validation rules.
hscontrol/noise.go Implements /machine/set-dns and refactors node validation helper.
hscontrol/mapper/mapper.go Advertises node FQDN via DNSConfig.CertDomains when enabled.
hscontrol/mapper/mapper_test.go Unit tests for CertDomains generation behavior.
hscontrol/dns/acme.go New embedded authoritative DNS server for ACME DNS-01 TXT + SOA/NS.
hscontrol/dns/acme_test.go Unit tests for ACME DNS server correctness (TXT, SOA/NS, expiry, etc.).
hscontrol/app.go Starts/stops the embedded authoritative DNS server when enabled.
go.mod Adds dependency on github.com/miekg/dns.
docs/ref/serve.md New documentation for HTTPS serve/cert setup and delegation.
docs/about/features.md Updates feature matrix to mark Serve as supported and link docs.
config-example.yaml Documents new dns.https_certs configuration stanza.
CHANGELOG.md Documents new HTTPS Serve/cert support and requirements.
.github/workflows/test-integration.yaml Adds new integration test to CI job list.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread hscontrol/app.go Outdated
Comment on lines +681 to +706
// Start the authoritative DNS server that answers ACME DNS-01
// challenges for per-node HTTPS certificates (tailscale serve /
// tailscale cert). base_domain must be publicly delegated to this
// server for the challenges to validate.
if h.cfg.DNSConfig.HTTPSCerts.Enabled {
listenAddr := h.cfg.DNSConfig.HTTPSCerts.ListenAddr
if listenAddr == "" {
listenAddr = ":53"
}

h.acmeDNS = dns.NewACMEChallengeServer(
h.cfg.BaseDomain,
h.cfg.DNSConfig.HTTPSCerts.Nameserver,
listenAddr,
)
if err := h.acmeDNS.Start(); err != nil {
return fmt.Errorf("starting ACME challenge DNS server: %w", err)
}
defer h.acmeDNS.Close()

log.Info().
Str("listen_addr", listenAddr).
Str("zone", h.cfg.BaseDomain).
Msg("authoritative DNS server for per-node HTTPS certificates started")
}
}
Comment thread hscontrol/app.go
Comment on lines +685 to +695
if h.cfg.DNSConfig.HTTPSCerts.Enabled {
listenAddr := h.cfg.DNSConfig.HTTPSCerts.ListenAddr
if listenAddr == "" {
listenAddr = ":53"
}

h.acmeDNS = dns.NewACMEChallengeServer(
h.cfg.BaseDomain,
h.cfg.DNSConfig.HTTPSCerts.Nameserver,
listenAddr,
)
Comment thread hscontrol/noise.go
Comment on lines +831 to +835
var dnsReq tailcfg.SetDNSRequest
if err := json.NewDecoder(req.Body).Decode(&dnsReq); err != nil {
httpError(writer, NewHTTPError(http.StatusBadRequest, "invalid SetDNSRequest body", err))
return
}
Comment on lines +170 to +174
if cfg.DNSConfig.HTTPSCerts.Enabled {
if fqdn, err := node.GetFQDN(cfg.BaseDomain); err == nil {
dnsConfig.CertDomains = []string{strings.TrimSuffix(fqdn, ".")}
}
}
Comment thread hscontrol/dns/acme.go
Comment on lines +94 to +99
func (s *ACMEChallengeServer) SetTXT(name, value string) {
const maxTXTValuesPerName = 8

name = dns.CanonicalName(name)
expires := time.Now().Add(acmeRecordTTL)
now := time.Now()
Comment thread docs/about/features.md
Comment on lines +43 to +46
- [ ] [Funnel](https://tailscale.com/docs/features/tailscale-funnel) ([#1040](https://github.com/juanfont/headscale/issues/1040)) — requires Tailscale's public ingress relays, which Headscale cannot provide
- [x] [Serve](https://tailscale.com/docs/features/tailscale-serve) ([#1921](https://github.com/juanfont/headscale/issues/1921))
- [x] HTTP serve (MagicDNS only)
- [x] [HTTPS serve / per-node TLS certificates](../ref/serve.md) (requires [`dns.https_certs`](../ref/serve.md) and a publicly delegated `base_domain`)
@B00M3000

B00M3000 commented Jun 8, 2026

Copy link
Copy Markdown
Author

#2527

B00M3000 and others added 2 commits June 13, 2026 00:18
The vendor hash was out of date with the latest go.mod changes from the
ACME DNS-01 server implementation. Re-computed the SRI hash based on the
current vendor directory state.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants