attempt to trigger ssl before first request

1. When AddDomain() is called (when proxy receives a new mapping), it now spawns a goroutine to prefetch the certificate
  2. prefetchCertificate() creates a synthetic tls.ClientHelloInfo and calls GetCertificate() to trigger the ACME flow
  3. The certificate is cached by autocert.DirCache, so subsequent real requests will use the cached cert
  4. If the cert is already cached (e.g., proxy restart), GetCertificate just returns it without making ACME requests
This commit is contained in:
mlsmaycon
2026-02-08 10:59:36 +01:00
parent dc26a5a436
commit 99e6b1eda4

View File

@@ -2,8 +2,11 @@ package acme
import (
"context"
"crypto/tls"
"fmt"
"net"
"sync"
"time"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/acme"
@@ -64,7 +67,6 @@ func (mgr *Manager) hostPolicy(ctx context.Context, domain string) error {
func (mgr *Manager) AddDomain(domain, accountID, reverseProxyID string) {
mgr.domainsMux.Lock()
defer mgr.domainsMux.Unlock()
mgr.domains[domain] = struct {
accountID string
reverseProxyID string
@@ -72,8 +74,44 @@ func (mgr *Manager) AddDomain(domain, accountID, reverseProxyID string) {
accountID: accountID,
reverseProxyID: reverseProxyID,
}
mgr.domainsMux.Unlock()
go mgr.prefetchCertificate(domain)
}
// prefetchCertificate proactively triggers certificate generation for a domain.
func (mgr *Manager) prefetchCertificate(domain string) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
hello := &tls.ClientHelloInfo{
ServerName: domain,
Conn: &dummyConn{ctx: ctx},
}
log.Infof("prefetching certificate for domain %q", domain)
_, err := mgr.GetCertificate(hello)
if err != nil {
log.Warnf("prefetch certificate for domain %q: %v", domain, err)
return
}
log.Infof("successfully prefetched certificate for domain %q", domain)
}
// dummyConn implements net.Conn to provide context for certificate fetching.
type dummyConn struct {
ctx context.Context
}
func (c *dummyConn) Read(b []byte) (n int, err error) { return 0, nil }
func (c *dummyConn) Write(b []byte) (n int, err error) { return len(b), nil }
func (c *dummyConn) Close() error { return nil }
func (c *dummyConn) LocalAddr() net.Addr { return nil }
func (c *dummyConn) RemoteAddr() net.Addr { return nil }
func (c *dummyConn) SetDeadline(t time.Time) error { return nil }
func (c *dummyConn) SetReadDeadline(t time.Time) error { return nil }
func (c *dummyConn) SetWriteDeadline(t time.Time) error { return nil }
func (mgr *Manager) RemoveDomain(domain string) {
mgr.domainsMux.Lock()
defer mgr.domainsMux.Unlock()