@@ -394,6 +394,7 @@ func (app *App) createAutomationPolicy(ctx caddy.Context) error {
 	if acmeIssuer.Challenges.HTTP.AlternatePort == 0 {
 		// don't overwrite existing explicit config
 		acmeIssuer.Challenges.HTTP.AlternatePort = app.HTTPPort
+		acmeIssuer.Provision(ctx)
 	}
 	if acmeIssuer.Challenges.TLSALPN == nil {
 		acmeIssuer.Challenges.TLSALPN = new(caddytls.TLSALPNChallengeConfig)
package caddyhttp

import (
	"fmt"
	"net/http"
	"strconv"

	"github.com/caddyserver/caddy/v2"
	"github.com/caddyserver/caddy/v2/modules/caddytls"
	"github.com/caddyserver/certmagic"
	"go.uber.org/zap"
)

// AutoHTTPSConfig is used to disable automatic HTTPS
// or certain aspects of it for a specific server.
// HTTPS is enabled automatically and by default when
// qualifying hostnames are available from the config.
type AutoHTTPSConfig struct {
	// If true, automatic HTTPS will be entirely disabled.
	Disabled bool `json:"disable,omitempty"`

	// If true, only automatic HTTP->HTTPS redirects will
	// be disabled.
	DisableRedir bool `json:"disable_redirects,omitempty"`

	// Hosts/domain names listed here will not be included
	// in automatic HTTPS (they will not have certificates
	// loaded nor redirects applied).
	Skip []string `json:"skip,omitempty"`

	// Hosts/domain names listed here will still be enabled
	// for automatic HTTPS (unless in the Skip list), except
	// that certificates will not be provisioned and managed
	// for these names.
	SkipCerts []string `json:"skip_certificates,omitempty"`

	// By default, automatic HTTPS will obtain and renew
	// certificates for qualifying hostnames. However, if
	// a certificate with a matching SAN is already loaded
	// into the cache, certificate management will not be
	// enabled. To force automated certificate management
	// regardless of loaded certificates, set this to true.
	IgnoreLoadedCerts bool `json:"ignore_loaded_certificates,omitempty"`
}

// Skipped returns true if name is in skipSlice, which
// should be either the Skip or SkipCerts field on ahc.
func (ahc AutoHTTPSConfig) Skipped(name string, skipSlice []string) bool {
	for _, n := range skipSlice {
		if name == n {
			return true
		}
	}
	return false
}

// automaticHTTPSPhase1 provisions all route matchers, determines
// which domain names found in the routes qualify for automatic
// HTTPS, and sets up HTTP->HTTPS redirects. This phase must occur
// at the beginning of provisioning, because it may add routes and
// even servers to the app, which still need to be set up with the
// rest of them during provisioning.
func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) error {
	// this map acts as a set to store the domain names
	// for which we will manage certificates automatically
	uniqueDomainsForCerts := make(map[string]struct{})

	// this maps domain names for automatic HTTP->HTTPS
	// redirects to their destination server address
	redirDomains := make(map[string]caddy.ParsedAddress)

	for srvName, srv := range app.Servers {
		// as a prerequisite, provision route matchers; this is
		// required for all routes on all servers, and must be
		// done before we attempt to do phase 1 of auto HTTPS,
		// since we have to access the decoded host matchers the
		// handlers will be provisioned later
		if srv.Routes != nil {
			err := srv.Routes.ProvisionMatchers(ctx)
			if err != nil {
				return fmt.Errorf("server %s: setting up route matchers: %v", srvName, err)
			}
		}

		// prepare for automatic HTTPS
		if srv.AutoHTTPS == nil {
			srv.AutoHTTPS = new(AutoHTTPSConfig)
		}
		if srv.AutoHTTPS.Disabled {
			continue
		}

		// skip if all listeners use the HTTP port
		if !srv.listenersUseAnyPortOtherThan(app.httpPort()) {
			app.logger.Info("server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server",
				zap.String("server_name", srvName),
				zap.Int("http_port", app.httpPort()),
			)
			srv.AutoHTTPS.Disabled = true
			continue
		}

		defaultConnPolicies := caddytls.ConnectionPolicies{
			&caddytls.ConnectionPolicy{ALPN: defaultALPN},
		}

		// if all listeners are on the HTTPS port, make sure
		// there is at least one TLS connection policy; it
		// should be obvious that they want to use TLS without
		// needing to specify one empty policy to enable it
		if srv.TLSConnPolicies == nil &&
			!srv.listenersUseAnyPortOtherThan(app.httpsPort()) {
			app.logger.Info("server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS",
				zap.String("server_name", srvName),
				zap.Int("https_port", app.httpsPort()),
			)
			srv.TLSConnPolicies = defaultConnPolicies
		}

		// find all qualifying domain names (deduplicated) in this server
		serverDomainSet := make(map[string]struct{})
		for routeIdx, route := range srv.Routes {
			for matcherSetIdx, matcherSet := range route.MatcherSets {
				for matcherIdx, m := range matcherSet {
					if hm, ok := m.(*MatchHost); ok {
						for hostMatcherIdx, d := range *hm {
							var err error
							d, err = repl.ReplaceOrErr(d, true, false)
							if err != nil {
								return fmt.Errorf("%s: route %d, matcher set %d, matcher %d, host matcher %d: %v",
									srvName, routeIdx, matcherSetIdx, matcherIdx, hostMatcherIdx, err)
							}
							if certmagic.HostQualifies(d) &&
								!srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.Skip) {
								serverDomainSet[d] = struct{}{}
							}
						}
					}
				}
			}
		}

		// nothing more to do here if there are no
		// domains that qualify for automatic HTTPS
		if len(serverDomainSet) == 0 {
			continue
		}

		// for all the hostnames we found, filter them so we have
		// a deduplicated list of names for which to obtain certs
		for d := range serverDomainSet {
			if !srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.SkipCerts) {
				// if a certificate for this name is already loaded,
				// don't obtain another one for it, unless we are
				// supposed to ignore loaded certificates
				if !srv.AutoHTTPS.IgnoreLoadedCerts &&
					len(app.tlsApp.AllMatchingCertificates(d)) > 0 {
					app.logger.Info("skipping automatic certificate management because one or more matching certificates are already loaded",
						zap.String("domain", d),
						zap.String("server_name", srvName),
					)
					continue
				}
				uniqueDomainsForCerts[d] = struct{}{}
			}
		}

		// tell the server to use TLS if it is not already doing so
		if srv.TLSConnPolicies == nil {
			srv.TLSConnPolicies = defaultConnPolicies
		}

		// nothing left to do if auto redirects are disabled
		if srv.AutoHTTPS.DisableRedir {
			continue
		}

		app.logger.Info("enabling automatic HTTP->HTTPS redirects",
			zap.String("server_name", srvName),
		)

		// create HTTP->HTTPS redirects
		for _, addr := range srv.Listen {
			// figure out the address we will redirect to...
			addr, err := caddy.ParseNetworkAddress(addr)
			if err != nil {
				return fmt.Errorf("%s: invalid listener address: %v", srvName, addr)
			}

			// ...and associate it with each domain in this server
			for d := range serverDomainSet {
				// if this domain is used on more than one HTTPS-enabled
				// port, we'll have to choose one, so prefer the HTTPS port
				if _, ok := redirDomains[d]; !ok ||
					addr.StartPort == uint(app.httpsPort()) {
					redirDomains[d] = addr
				}
			}
		}
	}

	// we now have a list of all the unique names for which we need certs;
	// turn the set into a slice so that phase 2 can use it
	app.allCertDomains = make([]string, 0, len(uniqueDomainsForCerts))
	for d := range uniqueDomainsForCerts {
		app.allCertDomains = append(app.allCertDomains, d)
	}

	// ensure there is an automation policy to handle these certs
	err := app.createAutomationPolicy(ctx)
	if err != nil {
		return err
	}

	// we're done if there are no HTTP->HTTPS redirects to add
	if len(redirDomains) == 0 {
		return nil
	}

	// we need to reduce the mapping, i.e. group domains by address
	// since new routes are appended to servers by their address
	domainsByAddr := make(map[string][]string)
	for domain, addr := range redirDomains {
		addrStr := addr.String()
		domainsByAddr[addrStr] = append(domainsByAddr[addrStr], domain)
	}

	// these keep track of the redirect server address(es)
	// and the routes for those servers which actually
	// respond with the redirects
	redirServerAddrs := make(map[string]struct{})
	var redirRoutes RouteList

	redirServers := make(map[string][]Route)

	for addrStr, domains := range domainsByAddr {
		// build the matcher set for this redirect route
		// (note that we happen to bypass Provision and
		// Validate steps for these matcher modules)
		matcherSet := MatcherSet{
			MatchProtocol("http"),
			MatchHost(domains),
		}

		// build the address to which to redirect
		addr, err := caddy.ParseNetworkAddress(addrStr)
		if err != nil {
			return err
		}
		redirTo := "https://{http.request.host}"
		if addr.StartPort != DefaultHTTPSPort {
			redirTo += ":" + strconv.Itoa(int(addr.StartPort))
		}
		redirTo += "{http.request.uri}"

		// build the route
		redirRoute := Route{
			MatcherSets: []MatcherSet{matcherSet},
			Handlers: []MiddlewareHandler{
				StaticResponse{
					StatusCode: WeakString(strconv.Itoa(http.StatusPermanentRedirect)),
					Headers: http.Header{
						"Location":   []string{redirTo},
						"Connection": []string{"close"},
					},
					Close: true,
				},
			},
		}

		// use the network/host information from the address,
		// but change the port to the HTTP port then rebuild
		redirAddr := addr
		redirAddr.StartPort = uint(app.httpPort())
		redirAddr.EndPort = redirAddr.StartPort
		redirAddrStr := redirAddr.String()

		redirServers[redirAddrStr] = append(redirServers[redirAddrStr], redirRoute)
	}

	// on-demand TLS means that hostnames may be used which are not
	// explicitly defined in the config, and we still need to redirect
	// those; so we can append a single catch-all route (notice there
	// is no Host matcher) after the other redirect routes which will
	// allow us to handle unexpected/new hostnames... however, it's
	// not entirely clear what the redirect destination should be,
	// so I'm going to just hard-code the app's HTTPS port and call
	// it good for now...
	appendCatchAll := func(routes []Route) []Route {
		redirTo := "https://{http.request.host}"
		if app.httpsPort() != DefaultHTTPSPort {
			redirTo += ":" + strconv.Itoa(app.httpsPort())
		}
		redirTo += "{http.request.uri}"
		routes = append(routes, Route{
			MatcherSets: []MatcherSet{MatcherSet{MatchProtocol("http")}},
			Handlers: []MiddlewareHandler{
				StaticResponse{
					StatusCode: WeakString(strconv.Itoa(http.StatusPermanentRedirect)),
					Headers: http.Header{
						"Location":   []string{redirTo},
						"Connection": []string{"close"},
					},
					Close: true,
				},
			},
		})
		return routes
	}

redirServersLoop:
	for redirServerAddr, routes := range redirServers {
		// for each redirect listener, see if there's already a
		// server configured to listen on that exact address; if so,
		// simply add the redirect route to the end of its route
		// list; otherwise, we'll create a new server for all the
		// listener addresses that are unused and serve the
		// remaining redirects from it
		for srvName, srv := range app.Servers {
			if srv.hasListenerAddress(redirServerAddr) {
				// user has configured a server for the same address
				// that the redirect runs from; simply append our
				// redirect route to the existing routes, with a
				// caveat that their config might override ours
				app.logger.Warn("user server is listening on same interface as automatic HTTP->HTTPS redirects; user-configured routes might override these redirects",
					zap.String("server_name", srvName),
					zap.String("interface", redirServerAddr),
				)
				srv.Routes = append(srv.Routes, appendCatchAll(routes)...)
				continue redirServersLoop
			}
		}

		// no server with this listener address exists;
		// save this address and route for custom server
		redirServerAddrs[redirServerAddr] = struct{}{}
		redirRoutes = append(redirRoutes, routes...)
	}

	// if there are routes remaining which do not belong
	// in any existing server, make our own to serve the
	// rest of the redirects
	if len(redirServerAddrs) > 0 {
		redirServerAddrsList := make([]string, 0, len(redirServerAddrs))
		for a := range redirServerAddrs {
			redirServerAddrsList = append(redirServerAddrsList, a)
		}
		app.Servers["remaining_auto_https_redirects"] = &Server{
			Listen: redirServerAddrsList,
			Routes: appendCatchAll(redirRoutes),
		}
	}

	return nil
}

// createAutomationPolicy ensures that certificates for this app are
// managed properly; for example, it's implied that the HTTPPort
// should also be the port the HTTP challenge is solved on; the same
// for HTTPS port and TLS-ALPN challenge also. We need to tell the
// TLS app to manage these certs by honoring those port configurations,
// so we either find an existing matching automation policy with an
// ACME issuer, or make a new one and append it.
func (app *App) createAutomationPolicy(ctx caddy.Context) error {
	var matchingPolicy *caddytls.AutomationPolicy
	var acmeIssuer *caddytls.ACMEIssuer
	if app.tlsApp.Automation != nil {
		// maybe we can find an exisitng one that matches; this is
		// useful if the user made a single automation policy to
		// set the CA endpoint to a test/staging endpoint (very
		// common), but forgot to customize the ports here, while
		// setting them in the HTTP app instead (I did this too
		// many times)
		for _, ap := range app.tlsApp.Automation.Policies {
			if len(ap.Hosts) == 0 {
				matchingPolicy = ap
				break
			}
		}
	}
	if matchingPolicy != nil {
		// if it has an ACME issuer, maybe we can just use that
		acmeIssuer, _ = matchingPolicy.Issuer.(*caddytls.ACMEIssuer)
	}
	if acmeIssuer == nil {
		acmeIssuer = new(caddytls.ACMEIssuer)
	}
	if acmeIssuer.Challenges == nil {
		acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
	}
	if acmeIssuer.Challenges.HTTP == nil {
		acmeIssuer.Challenges.HTTP = new(caddytls.HTTPChallengeConfig)
	}
	if acmeIssuer.Challenges.HTTP.AlternatePort == 0 {
		// don't overwrite existing explicit config
		acmeIssuer.Challenges.HTTP.AlternatePort = app.HTTPPort
	}
	if acmeIssuer.Challenges.TLSALPN == nil {
		acmeIssuer.Challenges.TLSALPN = new(caddytls.TLSALPNChallengeConfig)
	}
	if acmeIssuer.Challenges.TLSALPN.AlternatePort == 0 {
		// don't overwrite existing explicit config
		acmeIssuer.Challenges.TLSALPN.AlternatePort = app.HTTPSPort
	}

	if matchingPolicy == nil {
		// if there was no matching policy, we'll have to append our own
		err := app.tlsApp.AddAutomationPolicy(&caddytls.AutomationPolicy{
			Hosts:  app.allCertDomains,
			Issuer: acmeIssuer,
		})
		if err != nil {
			return err
		}
	} else {
		// if there was an existing matching policy, we need to reprovision
		// its issuer (because we just changed its port settings and it has
		// to re-build its stored certmagic config template with the new
		// values), then re-assign the Issuer pointer on the policy struct
		// because our type assertion changed the address
		err := acmeIssuer.Provision(ctx)
		if err != nil {
			return err
		}
		matchingPolicy.Issuer = acmeIssuer
	}

	return nil
}

// automaticHTTPSPhase2 begins certificate management for
// all names in the qualifying domain set for each server.
// This phase must occur after provisioning and at the end
// of app start, after all the servers have been started.
// Doing this last ensures that there won't be any race
// for listeners on the HTTP or HTTPS ports when management
// is async (if CertMagic's solvers bind to those ports
// first, then our servers would fail to bind to them,
// which would be bad, since CertMagic's bindings are
// temporary and don't serve the user's sites!).
func (app *App) automaticHTTPSPhase2() error {
	if len(app.allCertDomains) == 0 {
		return nil
	}
	app.logger.Info("enabling automatic TLS certificate management",
		zap.Strings("domains", app.allCertDomains),
	)
	err := app.tlsApp.Manage(app.allCertDomains)
	if err != nil {
		return fmt.Errorf("managing certificates for %v: %s", app.allCertDomains, err)
	}
	app.allCertDomains = nil // no longer needed; allow GC to deallocate
	return nil
}

Move this down to line 405 (old) or 406 (new).