@marcel91200 / caddy Mirror · read-only / commits / 929d0e50
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 16:00 UTC

caddyfile: Add `renewal_window_ratio` global option and `tls` subdirective (#7473)

* caddyfile: Add renewal_window_ratio global option Adds support for configuring the TLS certificate renewal window ratio directly in the Caddyfile global options block. This allows users to customize when certificates should be renewed without needing to use JSON configuration. Example usage: { renewal_window_ratio 0.1666 } Fixes #7467 * caddyfile: Add renewal_window_ratio to tls directive and tests Adds support for renewal_window_ratio in the tls directive (not just global options) and adds caddyfile adapt tests for both the global option and tls directive. * fix: inherit global renewal_window_ratio in site policies * fix: correct test expected output for policy consolidation * fix: properly inherit global renewal_window_ratio without removing other code

mehrdadbn9 committed Feb 14, 2026 at 01:17 UTC 929d0e502ad895737d7b0bafab18c63fb1d44cc1
5 files changed +160 -1
caddyconfig/httpcaddyfile/builtins.go
+24
@@ -113,6 +113,7 @@ func parseBind(h Helper) ([]ConfigValue, error) {
113 // issuer <module_name> [...]
114 // get_certificate <module_name> [...]
115 // insecure_secrets_log <log_file>
116 +// renewal_window_ratio <ratio>
117 // }
118 func parseTLS(h Helper) ([]ConfigValue, error) {
119 h.Next() // consume directive name
@@ -129,6 +130,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
130 var onDemand bool
131 var reusePrivateKeys bool
132 var forceAutomate bool
133 + var renewalWindowRatio float64
134
135 // Track which DNS challenge options are set
136 var dnsOptionsSet []string
@@ -473,6 +475,20 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
475 }
476 cp.InsecureSecretsLog = h.Val()
477
478 + case "renewal_window_ratio":
479 + arg := h.RemainingArgs()
480 + if len(arg) != 1 {
481 + return nil, h.ArgErr()
482 + }
483 + ratio, err := strconv.ParseFloat(arg[0], 64)
484 + if err != nil {
485 + return nil, h.Errf("parsing renewal_window_ratio: %v", err)
486 + }
487 + if ratio <= 0 || ratio >= 1 {
488 + return nil, h.Errf("renewal_window_ratio must be between 0 and 1 (exclusive)")
489 + }
490 + renewalWindowRatio = ratio
491 +
492 default:
493 return nil, h.Errf("unknown subdirective: %s", h.Val())
494 }
@@ -597,6 +613,14 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
613 })
614 }
615
616 + // renewal window ratio
617 + if renewalWindowRatio > 0 {
618 + configVals = append(configVals, ConfigValue{
619 + Class: "tls.renewal_window_ratio",
620 + Value: renewalWindowRatio,
621 + })
622 + }
623 +
624 // if enabled, the names in the site addresses will be
625 // added to the automation policies
626 if forceAutomate {
caddyconfig/httpcaddyfile/options.go
+20
@@ -65,6 +65,7 @@ func init() {
65 RegisterGlobalOption("persist_config", parseOptPersistConfig)
66 RegisterGlobalOption("dns", parseOptDNS)
67 RegisterGlobalOption("ech", parseOptECH)
68 + RegisterGlobalOption("renewal_window_ratio", parseOptRenewalWindowRatio)
69 }
70
71 func parseOptTrue(d *caddyfile.Dispenser, _ any) (any, error) { return true, nil }
@@ -624,3 +625,22 @@ func parseOptECH(d *caddyfile.Dispenser, _ any) (any, error) {
625
626 return ech, nil
627 }
628 +
629 +func parseOptRenewalWindowRatio(d *caddyfile.Dispenser, _ any) (any, error) {
630 + d.Next() // consume option name
631 + if !d.Next() {
632 + return 0, d.ArgErr()
633 + }
634 + val := d.Val()
635 + ratio, err := strconv.ParseFloat(val, 64)
636 + if err != nil {
637 + return 0, d.Errf("parsing renewal_window_ratio: %v", err)
638 + }
639 + if ratio <= 0 || ratio >= 1 {
640 + return 0, d.Errf("renewal_window_ratio must be between 0 and 1 (exclusive)")
641 + }
642 + if d.Next() {
643 + return 0, d.ArgErr()
644 + }
645 + return ratio, nil
646 +}
caddyconfig/httpcaddyfile/tlsapp.go
+12 -1
@@ -143,6 +143,12 @@ func (st ServerType) buildTLSApp(
143 ap.KeyType = keyTypeVals[0].Value.(string)
144 }
145
146 + if renewalWindowRatioVals, ok := sblock.pile["tls.renewal_window_ratio"]; ok {
147 + ap.RenewalWindowRatio = renewalWindowRatioVals[0].Value.(float64)
148 + } else if globalRenewalWindowRatio, ok := options["renewal_window_ratio"]; ok {
149 + ap.RenewalWindowRatio = globalRenewalWindowRatio.(float64)
150 + }
151 +
152 // certificate issuers
153 if issuerVals, ok := sblock.pile["tls.cert_issuer"]; ok {
154 var issuers []certmagic.Issuer
@@ -607,7 +613,8 @@ func newBaseAutomationPolicy(
613 _, hasLocalCerts := options["local_certs"]
614 keyType, hasKeyType := options["key_type"]
615 ocspStapling, hasOCSPStapling := options["ocsp_stapling"]
610 - hasGlobalAutomationOpts := hasIssuers || hasLocalCerts || hasKeyType || hasOCSPStapling
616 + renewalWindowRatio, hasRenewalWindowRatio := options["renewal_window_ratio"]
617 + hasGlobalAutomationOpts := hasIssuers || hasLocalCerts || hasKeyType || hasOCSPStapling || hasRenewalWindowRatio
618
619 globalACMECA := options["acme_ca"]
620 globalACMECARoot := options["acme_ca_root"]
@@ -654,6 +661,10 @@ func newBaseAutomationPolicy(
661 ap.OCSPOverrides = ocspConfig.ResponderOverrides
662 }
663
664 + if hasRenewalWindowRatio {
665 + ap.RenewalWindowRatio = renewalWindowRatio.(float64)
666 + }
667 +
668 return ap, nil
669 }
670
caddytest/integration/caddyfile_adapt/renewal_window_ratio_global.caddyfiletest new
+41
@@ -0,0 +1,41 @@
1 +{
2 + renewal_window_ratio 0.1666
3 +}
4 +
5 +example.com {
6 +}
7 +----------
8 +{
9 + "apps": {
10 + "http": {
11 + "servers": {
12 + "srv0": {
13 + "listen": [
14 + ":443"
15 + ],
16 + "routes": [
17 + {
18 + "match": [
19 + {
20 + "host": [
21 + "example.com"
22 + ]
23 + }
24 + ],
25 + "terminal": true
26 + }
27 + ]
28 + }
29 + }
30 + },
31 + "tls": {
32 + "automation": {
33 + "policies": [
34 + {
35 + "renewal_window_ratio": 0.1666
36 + }
37 + ]
38 + }
39 + }
40 + }
41 +}
caddytest/integration/caddyfile_adapt/renewal_window_ratio_tls_directive.caddyfiletest new
+63
@@ -0,0 +1,63 @@
1 +{
2 + renewal_window_ratio 0.1666
3 +}
4 +
5 +a.example.com {
6 + tls {
7 + renewal_window_ratio 0.25
8 + }
9 +}
10 +
11 +b.example.com {
12 +}
13 +----------
14 +{
15 + "apps": {
16 + "http": {
17 + "servers": {
18 + "srv0": {
19 + "listen": [
20 + ":443"
21 + ],
22 + "routes": [
23 + {
24 + "match": [
25 + {
26 + "host": [
27 + "a.example.com"
28 + ]
29 + }
30 + ],
31 + "terminal": true
32 + },
33 + {
34 + "match": [
35 + {
36 + "host": [
37 + "b.example.com"
38 + ]
39 + }
40 + ],
41 + "terminal": true
42 + }
43 + ]
44 + }
45 + }
46 + },
47 + "tls": {
48 + "automation": {
49 + "policies": [
50 + {
51 + "subjects": [
52 + "a.example.com"
53 + ],
54 + "renewal_window_ratio": 0.25
55 + },
56 + {
57 + "renewal_window_ratio": 0.1666
58 + }
59 + ]
60 + }
61 + }
62 + }
63 +}