diff --git a/common/tls/src/main/java/io/helidon/common/tls/TlsUtils.java b/common/tls/src/main/java/io/helidon/common/tls/TlsUtils.java
new file mode 100644
index 00000000000..dc18a41dd2e
--- /dev/null
+++ b/common/tls/src/main/java/io/helidon/common/tls/TlsUtils.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.common.tls;
+
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Optional;
+
+/**
+ * Utility class for TLS.
+ */
+public class TlsUtils {
+
+    private TlsUtils() {
+    }
+
+    /**
+     * Parse the Common Name (CN) from the first certificate if present.
+     *
+     * @param certificates certificates
+     * @return  Common Name value
+     */
+    public static Optional<String> parseCn(Certificate[] certificates) {
+        if (certificates.length >= 1) {
+            Certificate certificate = certificates[0];
+            X509Certificate cert = (X509Certificate) certificate;
+            Principal principal = cert.getSubjectX500Principal();
+            int i = 0;
+            String[] segments = principal.getName().split("=|,");
+            while (i + 1 < segments.length) {
+                if ("CN".equals(segments[i])) {
+                    return Optional.of(segments[i + 1]);
+                }
+                i += 2;
+            }
+            return Optional.of("Unknown CN");
+        }
+        return Optional.empty();
+    }
+}
diff --git a/examples/webserver/mutual-tls/src/main/java/io/helidon/examples/webserver/mtls/CertificateHelper.java b/examples/webserver/mutual-tls/src/main/java/io/helidon/examples/webserver/mtls/CertificateHelper.java
deleted file mode 100644
index c326cff9497..00000000000
--- a/examples/webserver/mutual-tls/src/main/java/io/helidon/examples/webserver/mtls/CertificateHelper.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (c) 2023 Oracle and/or its affiliates.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.helidon.examples.webserver.mtls;
-
-import java.util.Optional;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-class CertificateHelper {
-
-    private static final Pattern CN_PATTERN = Pattern.compile("(.*)CN=(.*?)(,.*)?");
-
-    private CertificateHelper() {
-    }
-
-    static Optional<String> clientCertificateName(String name) {
-        Matcher matcher = CN_PATTERN.matcher(name);
-        if (matcher.matches()) {
-            String cn = matcher.group(2);
-            if (!cn.isBlank()) {
-                return Optional.of(cn);
-            }
-        }
-        return Optional.empty();
-    }
-}
diff --git a/examples/webserver/mutual-tls/src/main/java/io/helidon/examples/webserver/mtls/SecureService.java b/examples/webserver/mutual-tls/src/main/java/io/helidon/examples/webserver/mtls/SecureService.java
index 298601d103a..232186f6ba8 100644
--- a/examples/webserver/mutual-tls/src/main/java/io/helidon/examples/webserver/mtls/SecureService.java
+++ b/examples/webserver/mutual-tls/src/main/java/io/helidon/examples/webserver/mtls/SecureService.java
@@ -15,25 +15,19 @@
  */
 package io.helidon.examples.webserver.mtls;
 
-import java.security.Principal;
-
 import io.helidon.http.Http;
 import io.helidon.webserver.http.HttpRules;
 import io.helidon.webserver.http.HttpService;
 
+import static io.helidon.http.Http.HeaderNames.X_HELIDON_CN;
+
 class SecureService implements HttpService {
     @Override
     public void routing(HttpRules rules) {
         rules.any((req, res) -> {
-            String cn = req.remotePeer()
-                    .tlsPrincipal()
-                    .map(Principal::getName)
-                    .flatMap(CertificateHelper::clientCertificateName)
-                    .orElse("Unknown CN");
-
             // close to avoid re-using cached connections on the client side
             res.header(Http.Headers.CONNECTION_CLOSE);
-            res.send("Hello " + cn + "!");
+            res.send("Hello " + req.headers().get(X_HELIDON_CN).value() + "!");
         });
     }
 }
diff --git a/http/http2/src/main/java/io/helidon/http/http2/Http2Headers.java b/http/http2/src/main/java/io/helidon/http/http2/Http2Headers.java
index 40333a7bebb..2846fe648f3 100644
--- a/http/http2/src/main/java/io/helidon/http/http2/Http2Headers.java
+++ b/http/http2/src/main/java/io/helidon/http/http2/Http2Headers.java
@@ -106,6 +106,7 @@ private Http2Headers(Headers httpHeaders, PseudoHeaders pseudoHeaders) {
      * @param stream  stream that owns these headers
      * @param table   dynamic table for this connection
      * @param huffman huffman decoder
+     * @param headers http2 headers
      * @param frames  frames of the headers
      * @return new headers parsed from the frames
      * @throws Http2Exception in case of protocol errors
@@ -113,6 +114,7 @@ private Http2Headers(Headers httpHeaders, PseudoHeaders pseudoHeaders) {
     public static Http2Headers create(Http2Stream stream,
                                       DynamicTable table,
                                       Http2HuffmanDecoder huffman,
+                                      Http2Headers headers,
                                       Http2FrameData... frames) {
 
         if (frames.length == 0) {
@@ -135,7 +137,7 @@ public static Http2Headers create(Http2Stream stream,
             stream.priority(priority);
         }
 
-        WritableHeaders<?> headers = WritableHeaders.create();
+        WritableHeaders<?> writableHeaders = WritableHeaders.create(headers.httpHeaders());
 
         BufferData[] buffers = new BufferData[frames.length];
         for (int i = 0; i < frames.length; i++) {
@@ -151,17 +153,34 @@ public static Http2Headers create(Http2Stream stream,
                 if (padLength > 0) {
                     data.skip(padLength);
                 }
-                return create(ServerRequestHeaders.create(headers), pseudoHeaders);
+                return create(ServerRequestHeaders.create(writableHeaders), pseudoHeaders);
             }
 
             if (data.available() == 0) {
                 throw new Http2Exception(Http2ErrorCode.PROTOCOL, "Expecting more header bytes");
             }
 
-            lastIsPseudoHeader = readHeader(headers, pseudoHeaders, table, huffman, data, lastIsPseudoHeader);
+            lastIsPseudoHeader = readHeader(writableHeaders, pseudoHeaders, table, huffman, data, lastIsPseudoHeader);
         }
     }
 
+    /**
+     * Create headers from HTTP request.
+     *
+     * @param stream  stream that owns these headers
+     * @param table   dynamic table for this connection
+     * @param huffman huffman decoder
+     * @param frames  frames of the headers
+     * @return new headers parsed from the frames
+     * @throws Http2Exception in case of protocol errors
+     */
+    public static Http2Headers create(Http2Stream stream,
+                                      DynamicTable table,
+                                      Http2HuffmanDecoder huffman,
+                                      Http2FrameData... frames) {
+        return create(stream, table, huffman, Http2Headers.create(WritableHeaders.create()), frames);
+    }
+
     /**
      * Create HTTP/2 headers from HTTP headers.
      *
diff --git a/webclient/tests/webclient/src/test/java/io/helidon/webclient/tests/CertificateHelper.java b/webclient/tests/webclient/src/test/java/io/helidon/webclient/tests/CertificateHelper.java
deleted file mode 100644
index 1de62d353b9..00000000000
--- a/webclient/tests/webclient/src/test/java/io/helidon/webclient/tests/CertificateHelper.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (c) 2023 Oracle and/or its affiliates.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.helidon.webclient.tests;
-
-import java.util.Optional;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-class CertificateHelper {
-
-    private static final Pattern CN_PATTERN = Pattern.compile("(.*)CN=(.*?)(,.*)?");
-
-    private CertificateHelper() {
-    }
-
-    static Optional<String> clientCertificateName(String name) {
-        Matcher matcher = CN_PATTERN.matcher(name);
-        if (matcher.matches()) {
-            String cn = matcher.group(2);
-            if (!cn.isBlank()) {
-                return Optional.of(cn);
-            }
-        }
-        return Optional.empty();
-    }
-}
diff --git a/webclient/tests/webclient/src/test/java/io/helidon/webclient/tests/MutualTlsTest.java b/webclient/tests/webclient/src/test/java/io/helidon/webclient/tests/MutualTlsTest.java
index c039298bcb8..42aa3fcf164 100644
--- a/webclient/tests/webclient/src/test/java/io/helidon/webclient/tests/MutualTlsTest.java
+++ b/webclient/tests/webclient/src/test/java/io/helidon/webclient/tests/MutualTlsTest.java
@@ -16,7 +16,6 @@
 package io.helidon.webclient.tests;
 
 import java.io.UncheckedIOException;
-import java.security.Principal;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import io.helidon.http.Http;
@@ -32,6 +31,8 @@
 
 import org.junit.jupiter.api.Test;
 
+import static io.helidon.http.Http.HeaderNames.X_HELIDON_CN;
+
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.endsWith;
 import static org.hamcrest.CoreMatchers.is;
@@ -181,15 +182,10 @@ private static void plainRouting(HttpRouting.Builder routing) {
 
     private static void mTlsRouting(HttpRouting.Builder routing) {
         routing.get("/", (req, res) -> {
-            String cn = req.remotePeer()
-                    .tlsPrincipal()
-                    .map(Principal::getName)
-                    .flatMap(CertificateHelper::clientCertificateName)
-                    .orElse("Unknown CN");
 
             // close to avoid re-using cached connections on the client side
             res.header(Http.Headers.CONNECTION_CLOSE);
-            res.send("Hello " + cn + "!");
+            res.send("Hello " + req.headers().value(X_HELIDON_CN).orElse("Unknown CN") + "!");
         });
     }
 
diff --git a/webserver/http2/src/main/java/io/helidon/webserver/http2/Http2Connection.java b/webserver/http2/src/main/java/io/helidon/webserver/http2/Http2Connection.java
index 3a1525a707e..c8bc9183c04 100644
--- a/webserver/http2/src/main/java/io/helidon/webserver/http2/Http2Connection.java
+++ b/webserver/http2/src/main/java/io/helidon/webserver/http2/Http2Connection.java
@@ -29,9 +29,11 @@
 import io.helidon.common.buffers.BufferData;
 import io.helidon.common.buffers.DataReader;
 import io.helidon.common.task.InterruptableTask;
+import io.helidon.common.tls.TlsUtils;
 import io.helidon.http.Http;
 import io.helidon.http.Http.HeaderNames;
 import io.helidon.http.HttpPrologue;
+import io.helidon.http.WritableHeaders;
 import io.helidon.http.http2.ConnectionFlowControl;
 import io.helidon.http.http2.Http2ConnectionWriter;
 import io.helidon.http.http2.Http2ErrorCode;
@@ -61,6 +63,7 @@
 import io.helidon.webserver.http2.spi.Http2SubProtocolSelector;
 import io.helidon.webserver.spi.ServerConnection;
 
+import static io.helidon.http.Http.HeaderNames.X_HELIDON_CN;
 import static io.helidon.http.http2.Http2Util.PREFACE_LENGTH;
 import static java.lang.System.Logger.Level.DEBUG;
 import static java.lang.System.Logger.Level.TRACE;
@@ -92,6 +95,7 @@ public class Http2Connection implements ServerConnection, InterruptableTask<Void
     private final Http2Settings serverSettings;
     private final boolean sendErrorDetails;
     private final ConnectionFlowControl flowControl;
+    private final WritableHeaders<?> connectionHeaders;
 
     // initial client settings, until we receive real ones
     private Http2Settings clientSettings = Http2Settings.builder()
@@ -106,6 +110,7 @@ public class Http2Connection implements ServerConnection, InterruptableTask<Void
     private int continuationExpectedStreamId;
     private int lastStreamId;
     private long maxClientConcurrentStreams;
+    private boolean initConnectionHeaders;
     private volatile ZonedDateTime lastRequestTimestamp;
     private volatile Thread myThread;
     private volatile boolean canRun = true;
@@ -136,6 +141,8 @@ public class Http2Connection implements ServerConnection, InterruptableTask<Void
                 .maxFrameSize(http2Config.maxFrameSize())
                 .build();
         this.lastRequestTimestamp = Http.DateTime.timestamp();
+        this.connectionHeaders = WritableHeaders.create();
+        this.initConnectionHeaders = true;
     }
 
     private static void settingsUpdate(Http2Config config, Http2Settings.Builder builder) {
@@ -588,12 +595,19 @@ private void doHeaders(Semaphore requestSemaphore) {
         boolean endOfStream;
         Http2Headers headers;
         Http2ServerStream stream = streamContext.stream();
+        if (initConnectionHeaders) {
+            ctx.remotePeer().tlsCertificates()
+                    .flatMap(TlsUtils::parseCn)
+                    .ifPresent(cn -> connectionHeaders.add(X_HELIDON_CN, cn));
+            initConnectionHeaders = false;
+        }
 
         if (frameHeader.type() == Http2FrameType.CONTINUATION) {
             // end of continuations with header frames
             headers = Http2Headers.create(stream,
                                           requestDynamicTable,
                                           requestHuffman,
+                                          Http2Headers.create(connectionHeaders),
                                           streamContext.contData());
             endOfStream = streamContext.contHeader().flags(Http2FrameTypes.HEADERS).endOfStream();
             streamContext.clearContinuations();
@@ -603,6 +617,7 @@ private void doHeaders(Semaphore requestSemaphore) {
             headers = Http2Headers.create(stream,
                                           requestDynamicTable,
                                           requestHuffman,
+                                          Http2Headers.create(connectionHeaders),
                                           new Http2FrameData(frameHeader, inProgressFrame()));
         }
 
diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/http1/Http1Connection.java b/webserver/webserver/src/main/java/io/helidon/webserver/http1/Http1Connection.java
index 9ce9c65f18e..de51e64e044 100644
--- a/webserver/webserver/src/main/java/io/helidon/webserver/http1/Http1Connection.java
+++ b/webserver/webserver/src/main/java/io/helidon/webserver/http1/Http1Connection.java
@@ -29,6 +29,7 @@
 import io.helidon.common.buffers.DataWriter;
 import io.helidon.common.mapper.MapperException;
 import io.helidon.common.task.InterruptableTask;
+import io.helidon.common.tls.TlsUtils;
 import io.helidon.http.BadRequestException;
 import io.helidon.http.DirectHandler;
 import io.helidon.http.DirectHandler.EventType;
@@ -49,6 +50,7 @@
 import io.helidon.webserver.http1.spi.Http1Upgrader;
 import io.helidon.webserver.spi.ServerConnection;
 
+import static io.helidon.http.Http.HeaderNames.X_HELIDON_CN;
 import static java.lang.System.Logger.Level.TRACE;
 import static java.lang.System.Logger.Level.WARNING;
 
@@ -136,6 +138,9 @@ public void handle(Semaphore requestSemaphore) throws InterruptedException {
                 currentEntitySizeRead = 0;
 
                 WritableHeaders<?> headers = http1headers.readHeaders(prologue);
+                ctx.remotePeer().tlsCertificates()
+                        .flatMap(TlsUtils::parseCn)
+                        .ifPresent(name -> headers.set(X_HELIDON_CN, name));
                 recvListener.headers(ctx, headers);
 
                 if (canUpgrade) {