diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go
index 103b7a1f8..7f23fd59d 100644
--- a/caddyconfig/httpcaddyfile/builtins.go
+++ b/caddyconfig/httpcaddyfile/builtins.go
@@ -53,8 +53,7 @@ func init() {
 
 // parseBind parses the bind directive. Syntax:
 //
-//     bind <addresses...>
-//
+//	bind <addresses...>
 func parseBind(h Helper) ([]ConfigValue, error) {
 	var lnHosts []string
 	for h.Next() {
@@ -65,28 +64,28 @@ func parseBind(h Helper) ([]ConfigValue, error) {
 
 // parseTLS parses the tls directive. Syntax:
 //
-//     tls [<email>|internal]|[<cert_file> <key_file>] {
-//         protocols <min> [<max>]
-//         ciphers   <cipher_suites...>
-//         curves    <curves...>
-//         client_auth {
-//             mode                   [request|require|verify_if_given|require_and_verify]
-//             trusted_ca_cert        <base64_der>
-//             trusted_ca_cert_file   <filename>
-//             trusted_leaf_cert      <base64_der>
-//             trusted_leaf_cert_file <filename>
-//         }
-//         alpn      <values...>
-//         load      <paths...>
-//         ca        <acme_ca_endpoint>
-//         ca_root   <pem_file>
-//         dns       <provider_name> [...]
-//         on_demand
-//         eab    <key_id> <mac_key>
-//         issuer <module_name> [...]
-//         get_certificate <module_name> [...]
-//     }
-//
+//	tls [<email>|internal]|[<cert_file> <key_file>] {
+//	    protocols <min> [<max>]
+//	    ciphers   <cipher_suites...>
+//	    curves    <curves...>
+//	    client_auth {
+//	        mode                   [request|require|verify_if_given|require_and_verify]
+//	        trusted_ca_cert        <base64_der>
+//	        trusted_ca_cert_file   <filename>
+//	        trusted_leaf_cert      <base64_der>
+//	        trusted_leaf_cert_file <filename>
+//	    }
+//	    alpn      <values...>
+//	    load      <paths...>
+//	    ca        <acme_ca_endpoint>
+//	    ca_root   <pem_file>
+//	    dns       <provider_name> [...]
+//	    on_demand
+//	    eab    <key_id> <mac_key>
+//	    issuer <module_name> [...]
+//	    get_certificate <module_name> [...]
+//	    insecure_secrets_log <log_file>
+//	}
 func parseTLS(h Helper) ([]ConfigValue, error) {
 	cp := new(caddytls.ConnectionPolicy)
 	var fileLoader caddytls.FileLoader
@@ -396,6 +395,12 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
 				}
 				onDemand = true
 
+			case "insecure_secrets_log":
+				if !h.NextArg() {
+					return nil, h.ArgErr()
+				}
+				cp.InsecureSecretsLog = h.Val()
+
 			default:
 				return nil, h.Errf("unknown subdirective: %s", h.Val())
 			}
@@ -516,8 +521,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
 
 // parseRoot parses the root directive. Syntax:
 //
-//     root [<matcher>] <path>
-//
+//	root [<matcher>] <path>
 func parseRoot(h Helper) (caddyhttp.MiddlewareHandler, error) {
 	var root string
 	for h.Next() {
@@ -695,12 +699,11 @@ func parseHandleErrors(h Helper) ([]ConfigValue, error) {
 
 // parseLog parses the log directive. Syntax:
 //
-//     log {
-//         output <writer_module> ...
-//         format <encoder_module> ...
-//         level  <level>
-//     }
-//
+//	log {
+//	    output <writer_module> ...
+//	    format <encoder_module> ...
+//	    level  <level>
+//	}
 func parseLog(h Helper) ([]ConfigValue, error) {
 	return parseLogHelper(h, nil)
 }
diff --git a/caddyconfig/httpcaddyfile/options.go b/caddyconfig/httpcaddyfile/options.go
index c41e8bc8a..1775c93b6 100644
--- a/caddyconfig/httpcaddyfile/options.go
+++ b/caddyconfig/httpcaddyfile/options.go
@@ -421,13 +421,13 @@ func parseOCSPStaplingOptions(d *caddyfile.Dispenser, _ any) (any, error) {
 
 // parseLogOptions parses the global log option. Syntax:
 //
-//     log [name] {
-//         output  <writer_module> ...
-//         format  <encoder_module> ...
-//         level   <level>
-//         include <namespaces...>
-//         exclude <namespaces...>
-//     }
+//	log [name] {
+//	    output  <writer_module> ...
+//	    format  <encoder_module> ...
+//	    level   <level>
+//	    include <namespaces...>
+//	    exclude <namespaces...>
+//	}
 //
 // When the name argument is unspecified, this directive modifies the default
 // logger.
diff --git a/modules/caddytls/connpolicy.go b/modules/caddytls/connpolicy.go
index f7b9c4622..bce69bc83 100644
--- a/modules/caddytls/connpolicy.go
+++ b/modules/caddytls/connpolicy.go
@@ -20,11 +20,14 @@ import (
 	"encoding/base64"
 	"encoding/json"
 	"fmt"
+	"io"
 	"os"
+	"path/filepath"
 	"strings"
 
 	"github.com/caddyserver/caddy/v2"
 	"github.com/mholt/acmez"
+	"go.uber.org/zap"
 )
 
 func init() {
@@ -156,6 +159,16 @@ type ConnectionPolicy struct {
 	// is no policy configured for the empty SNI value.
 	DefaultSNI string `json:"default_sni,omitempty"`
 
+	// Also known as "SSLKEYLOGFILE", TLS secrets will be written to
+	// this file in NSS key log format which can then be parsed by
+	// Wireshark and other tools. This is INSECURE as it allows other
+	// programs or tools to decrypt TLS connections. However, this
+	// capability can be useful for debugging and troubleshooting.
+	// **ENABLING THIS LOG COMPROMISES SECURITY!**
+	//
+	// This feature is EXPERIMENTAL and subject to change or removal.
+	InsecureSecretsLog string `json:"insecure_secrets_log,omitempty"`
+
 	// TLSConfig is the fully-formed, standard lib TLS config
 	// used to serve TLS connections. Provision all
 	// ConnectionPolicies to populate this. It is exported only
@@ -280,6 +293,30 @@ func (p *ConnectionPolicy) buildStandardTLSConfig(ctx caddy.Context) error {
 		}
 	}
 
+	if p.InsecureSecretsLog != "" {
+		filename, err := caddy.NewReplacer().ReplaceOrErr(p.InsecureSecretsLog, true, true)
+		if err != nil {
+			return err
+		}
+		filename, err = filepath.Abs(filename)
+		if err != nil {
+			return err
+		}
+		logFile, _, err := secretsLogPool.LoadOrNew(filename, func() (caddy.Destructor, error) {
+			w, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
+			return destructableWriter{w}, err
+		})
+		if err != nil {
+			return err
+		}
+		ctx.OnCancel(func() { _, _ = secretsLogPool.Delete(filename) })
+
+		cfg.KeyLogWriter = logFile.(io.Writer)
+
+		tlsApp.logger.Warn("TLS SECURITY COMPROMISED: secrets logging is enabled!",
+			zap.String("log_filename", filename))
+	}
+
 	setDefaultTLSParams(cfg)
 
 	p.TLSConfig = cfg
@@ -297,7 +334,8 @@ func (p ConnectionPolicy) SettingsEmpty() bool {
 		p.ProtocolMin == "" &&
 		p.ProtocolMax == "" &&
 		p.ClientAuthentication == nil &&
-		p.DefaultSNI == ""
+		p.DefaultSNI == "" &&
+		p.InsecureSecretsLog == ""
 }
 
 // ClientAuthentication configures TLS client auth.
@@ -542,3 +580,9 @@ type ClientCertificateVerifier interface {
 }
 
 var defaultALPN = []string{"h2", "http/1.1"}
+
+type destructableWriter struct{ *os.File }
+
+func (d destructableWriter) Destruct() error { return d.Close() }
+
+var secretsLogPool = caddy.NewUsagePool()