Recommended Apache Configuration

This page describes the recommended OS-provided Apache reverse proxy configuration for MTG services, with TLS 1.3 support and one public hostname per service group. For the package replacement procedure, see Migrating from MTG-Apache2 to the OS Apache Package.

The customer is responsible for configuring and operating the reverse proxy, including its security, maintenance, updates and monitoring. Moving to the OS-provided Apache package places responsibility for this layer with the customer rather than MTG.

The configuration on this page is a recommended baseline covering the reverse proxy requirements of the MTG services described here, including TLS 1.3 and client certificate authentication where needed. It is not a universal configuration or a prescribed solution for every deployment. The best setup depends on your use case, deployed services, network architecture and security requirements. Adapt and validate the recommendations for your environment before putting them into operation.

See Supported Platforms for the supported operating system releases. Verify the configuration against the Apache/OpenSSL packages and crypto policy shipped with your supported OS release.

The CARA configuration and authentication model on this page apply exclusively to CARA 3.x.

Virtual Host Configuration Changes with TLS 1.3

Some legacy configurations publish several services under one hostname and change SSLVerifyClient inside <Location> blocks. For example, the legacy virtual host requested client certificates optionally, /clm-ui/ disabled client certificate authentication and /cara-admin/secure required it. Changes to client certificate requirements after the initial handshake can trigger another certificate exchange. With TLS 1.2, Apache can use handshake renegotiation to do this after reading the requested HTTP path.

TLS 1.3 removes handshake renegotiation. Its alternative, post-handshake authentication (PHA), requires client support and cannot be assumed to work across browsers, enrollment clients and other TLS stacks. With a per-location certificate request, clients that do not offer PHA can receive HTTP 403 with Apache error AH10158 , even on a path intended to allow anonymous access. HTTP/2 prohibits TLS 1.3 post-handshake authentication altogether, as specified by RFC 8740.

Set SSLVerifyClient at virtual host level so Apache requests the certificate during the initial TLS handshake. Separate services with different certificate request policies by hostname, allowing SNI to select the policy before the HTTP path is available. For details on the directive’s scope, see the Apache mod_ssl documentation.

If mTLS client authentication is not used, no additional virtual hosts are required for TLS 1.3. You can retain the existing hostnames and path-based proxy layout with SSLVerifyClient none. The service-per-host layout below is a recommended baseline, not a requirement imposed by the package replacement.

Clients must connect to the mTLS hostname directly. A redirect from an old hostname happens after its TLS handshake and does not repair a failed certificate exchange. Use distinct names rather than creating multiple virtual hosts with the same ServerName for different paths.

Service-per-Host Baseline

The example uses node01.example.com as the node name and the following service names. All names may resolve to the same Apache instance or load balancer.

These configuration examples cover the ERS suite. Configure only the components used in your deployment.
Public hostname Services and paths Client certificate policy

clm.node01.example.com

CLM API /clm-api, CLM UI /clm-ui, ACME /acme, CMP /cmp, SCEP /scep, AEC /aec; revocation information /rev-info on HTTP

Always none; authentication is handled by Keycloak

auth.node01.example.com

Keycloak /auth

optional for X.509 login; use none if X.509 login is not used

est.node01.example.com

EST /.well-known/est and /est

optional; HTTP Basic authentication and mTLS are handled by mtg-est-server

cara.node01.example.com

CARA 3.x web services /cara-ws and admin UI /cara-admin-ui

Always none; authentication is handled by Keycloak

kms.node01.example.com

KMS /kms, KMS UI /kms-ui, crypto API /kms-crypto-api

Always none; authentication is handled by Keycloak

CLM, CARA 3.x and KMS always use SSLVerifyClient none on their Apache virtual hosts. Authentication for these services is handled by Keycloak, including X.509 login through the authentication hostname when configured. Their Apache virtual hosts do not request client certificates.

EST is the exception: the standard supports both HTTP Basic authentication and mTLS client authentication. Its Apache virtual host uses SSLVerifyClient optional so clients can authenticate with HTTP Basic credentials without presenting a certificate, or present a client certificate for mTLS. Authentication for EST is handled by mtg-est-server, not Keycloak. Anonymous operations such as retrieving CA certificates remain available where permitted by the EST server.

optional requests a certificate from every connection to that hostname. It permits a client to send no certificate, but rejects a presented certificate that cannot be validated against the configured client CA trust. The application still decides whether a particular operation requires an authenticated identity. Do not use optional_no_ca as a substitute for verified client authentication.

Do not set SSLVerifyClient require on the EST virtual host: doing so would prevent HTTP Basic authentication without a client certificate and block anonymous operations.

DNS, Certificates and Application URLs

Create DNS records for the chosen public service names. The server certificate must cover every name it serves through its subject alternative names (SANs). The examples use a shared full-chain certificate named after the node, containing all five service names as SANs; separate certificates per service are also possible.

Update application public URLs, Keycloak frontend and issuer settings, allowed redirect URIs, browser integrations and enrollment client endpoints to match the chosen names. Check the URLs returned by discovery endpoints and redirects after the change. Retain old HTTP revocation URLs already embedded in certificates, even when other services move to new names.

OS-Specific Paths and Modules

Setting RHEL Ubuntu SUSE

Service

httpd

apache2

apache2

Main configuration

/etc/httpd/conf/httpd.conf

/etc/apache2/apache2.conf

/etc/apache2/httpd.conf

Virtual host files

/etc/httpd/conf.d/

/etc/apache2/sites-available/, enabled through sites-enabled/

/etc/apache2/vhosts.d/

Server certificates and client CA bundle

/etc/pki/tls/certs/

/etc/ssl/certs/

/etc/apache2/ssl.crt/

Private keys

/etc/pki/tls/private/

/etc/ssl/private/

/etc/apache2/ssl.key/

Logs

/var/log/httpd/

/var/log/apache2/

/var/log/apache2/

Stapling cache directory

/run/httpd

/var/run/apache2

/var/lib/apache2

Enable ssl, socache_shmcb, headers, proxy, proxy_http, rewrite and alias. Enable http2 for HTTP/2 support and proxy_ajp if deploying the KMS AJP example. Keep authz_core loaded for the access rules. Load modules before deploying configuration that uses their directives.

On Ubuntu:

a2enmod ssl socache_shmcb headers proxy proxy_http rewrite alias http2
# Only for the KMS AJP configuration:
a2enmod proxy_ajp

Enable each deployed file with a2ensite, for example a2ensite clm.node01.example.com.conf. Enable 000-default.conf and 10-mtg_ssl_hardening.conf in the same way when using the configuration examples.

On RHEL, install mod_ssl and mod_http2 alongside httpd and check module loading under /etc/httpd/conf.modules.d/. On SUSE, enable the same required modules with a2enmod, select the event MPM in /etc/sysconfig/apache2 (APACHE_MPM="event") and enable HTTPS with a2enflag SSL.

Use the distribution’s module binaries and listener configuration. Do not copy the legacy LoadModule paths, ServerRoot, PID settings or /opt/apache2 paths into the new main configuration. Replace legacy Order and Deny access rules with Apache 2.4 Require rules while preserving their intended access restrictions.

Listeners and Default Virtual Host

Configure ports 80 and 443 once per Apache instance. Do not copy Listen 443 into every service file. On RHEL, the example ssl.conf retains the server-wide SSL directives and listener while removing the stock default:443 virtual host. On Ubuntu and SUSE, retain the distribution’s listener configuration and disable unused stock sites.

Load 000-default.conf before the service virtual hosts. It returns HTTP 421 for unconfigured names, including the bare node name, while still serving HTTP-01 challenges on port 80. A TLS handshake must complete before an HTTPS error response can be returned, so it also needs a valid server certificate. Remove or disable stock welcome pages and directory listings that would otherwise remain accessible.

TLS Policy

The server-wide configuration enables TLS 1.3 and TLS 1.2, disables TLS 1.0 and TLS 1.1 and configures separate cipher lists for TLS 1.2 and TLS 1.3. Remove conflicting per-virtual-host legacy settings such as SSLProtocol TLSv1.2 so the shared policy takes effect.

SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite TLSv1.3 TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_SHA256
SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-CCM:ECDHE-ECDSA-AES128-CCM:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256
SSLHonorCipherOrder on
SSLCompression off
SSLSessionTickets off
Protocols h2 http/1.1

The TLS 1.3 Brainpool groups in the configuration require OpenSSL 3.2 or newer. On OpenSSL versions older than 3.2, including OpenSSL 3.0 shipped with Ubuntu 24.04, remove the TLS 1.3 Brainpool names and use the following variant:

SSLOpenSSLConfCmd Groups secp256r1:secp384r1:secp521r1:brainpoolP256r1:brainpoolP384r1:brainpoolP512r1

Unknown group names can prevent Apache from starting. Verify support in the Apache-linked OpenSSL library and test actual negotiated protocols and ciphers against the OS crypto policy. The examples leave handshake signature algorithms at OpenSSL’s defaults. Do not carry over restrictive legacy SignatureAlgorithms settings without testing server and client certificates, including RSA clients' ability to produce TLS 1.3 RSA-PSS handshake signatures.

The configuration enables server certificate OCSP stapling. Adapt SSLStaplingCache to the OS-specific runtime directory and ensure the server certificate has a usable OCSP responder and Apache can reach it. Client certificate OCSP validation (SSLOCSPEnable) is a separate setting. Preserve the installation’s existing client revocation policy; the example virtual hosts do not enable that policy automatically.

Client Certificate Forwarding and Backend Trust

The EST and authentication virtual hosts configure the following at virtual host level:

SSLVerifyClient optional
SSLVerifyDepth 2
SSLCACertificateFile /etc/pki/tls/certs/trusted-ca.pem

RequestHeader unset X-SSL-Client-Verify
RequestHeader unset X-SSL-Client-S-DN
RequestHeader unset x-ssl-client-cert
RequestHeader set X-SSL-Client-Verify "%{SSL_CLIENT_VERIFY}s" "expr=%{SSL_CLIENT_VERIFY} == 'SUCCESS'"
RequestHeader set X-SSL-Client-S-DN "%{SSL_CLIENT_S_DN}s" "expr=%{SSL_CLIENT_VERIFY} == 'SUCCESS'"
RequestHeader set x-ssl-client-cert "%{SSL_CLIENT_CERT}s" "expr=%{SSL_CLIENT_VERIFY} == 'SUCCESS'"

Adapt the CA bundle path and verification depth to the deployed client certificate hierarchy. The client CA bundle must trust the issuers of the client certificates, which may differ from the server certificate’s issuers.

Unset incoming certificate headers before setting them from a successfully verified TLS certificate. The examples consistently use x-ssl-client-cert, matching the header name expected by the MTG applications. HTTP header names are case-insensitive, as specified by RFC 9110, Section 5.1. Apache’s RequestHeader directive also matches header names without regard to case, so the unset rule removes client-supplied variants of this header before the verified certificate is forwarded. Strip any additional identity or certificate-chain headers trusted by your backend. On hosts using SSLVerifyClient none, remove these headers too if the backend could interpret them as an authenticated identity.

Use the conditional RequestHeader expressions shown above rather than wrapping the forwarding rules in <If> blocks based on SSL_CLIENT_VERIFY. Combining per-location authentication with such blocks can cause the certificate headers to be omitted on the first request of a connection.

For EST over HTTP proxying, enable tomcat.ssl-valve.enabled=true and ensure tomcat.ssl-valve.ssl-client-cert-header matches the forwarded header. See MTG EST Configuration. For Keycloak X.509 login, configure its certificate lookup and authentication flow for the trusted proxy headers. The example sanitizes incoming forwarding headers and sets X-Forwarded-Proto: https and X-Forwarded-Port: 443; Keycloak must use the corresponding xforwarded proxy header mode.

Bind backend listeners to loopback when Apache and the applications share a node, or restrict access to the trusted proxy on separate nodes. Clients must not be able to bypass Apache and submit trusted certificate headers directly to the backend. Where SELinux enforces network restrictions, permit the required proxy connections through the applicable policy. Use setsebool -P httpd_can_network_connect on to permit proxy connections, or configure a local policy that restricts this permission more narrowly. Apply the correct file labels to copied certificate material and account for any AppArmor restrictions on SUSE or Ubuntu.

HTTP Exceptions and Proxy Targets

Every example port-80 virtual host serves ACME HTTP-01 files from /var/www/html/.well-known/acme-challenge/ before redirecting other traffic to HTTPS. Create that directory if using this challenge method and update the renewal client’s webroot setting. The CLM HTTP virtual host also proxies /rev-info to the CARA revocation information service on port 8906 without redirecting it to HTTPS. Clients fetching revocation information must not depend on successfully validating the same TLS service first.

The examples use the following backend listeners and context paths:

Public path Backend

/clm-api

127.0.0.1:8501/clm-api

/clm-ui

127.0.0.1:3000/clm-ui

/auth

127.0.0.1:8505/auth

/auth/health

127.0.0.1:9000/auth/health

/acme

127.0.0.1:8502/acme

/cmp

127.0.0.1:8504/cmp

/aec

127.0.0.1:8506/aec

/scep

127.0.0.1:8507/scep

/.well-known/est

127.0.0.1:8503/est/.well-known/est

/est

127.0.0.1:8503/est

/cara-ws

127.0.0.1:8902/cara-ws

/cara-admin-ui

127.0.0.1:3007/cara-admin-ui

/rev-info (HTTP)

127.0.0.1:8906/rev-info

/kms

ajp://127.0.0.1:8801/kms

/kms-ui

127.0.0.1:3005/kms-ui

/kms-crypto-api

ajp://127.0.0.1:8807/kms-crypto-api

Keep more specific proxy mappings before broader ones, such as /auth/health before /auth. Preserve matching path and trailing slash semantics in ProxyPass and ProxyPassReverse. Decide whether the Keycloak management health endpoint should be publicly reachable or restricted to monitoring systems.

For KMS, replace <KMS_AJP_SECRET> and <CRYPTO_API_AJP_SECRET> with the secrets configured on the corresponding AJP connectors and protect the configuration files. Keep the KMS UI base URL aligned with /kms-ui. The PKCS#11 server terminates its own TLS and KMIP uses its own TCP listener; neither is published by these HTTP virtual hosts.

Backend paths depend on the application version. The CARA examples apply exclusively to CARA 3.x. A legacy EST deployment may serve /.well-known/est without the /est context prefix. Preserve working backend paths during a package-only migration, even when moving their public hostname.

Configuration Examples

Expand each configuration below to view and copy it. The examples use placeholder names and Red Hat paths. Adapt them using the OS path table and TLS compatibility notes above before enabling them. They are configuration examples, not a complete deployment for every MTG version.

Default virtual host: 000-default.conf
# Example configuration with placeholder names and Red Hat paths.
# Adapt using the Recommended Apache Configuration guide before deployment.
<VirtualHost *:80>
  ServerName default.invalid

  Alias /.well-known/acme-challenge/ /var/www/html/.well-known/acme-challenge/

  <Directory /var/www/html/.well-known/acme-challenge/>
      Options None
      AllowOverride None
      Require all granted
      ForceType text/plain
  </Directory>

  RewriteEngine On
  RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge/
  RewriteRule ^ - [R=421,L]
</VirtualHost>

<VirtualHost *:443>
  ServerName default.invalid

  CustomLog /var/log/httpd/default-access.log combined
  ErrorLog  /var/log/httpd/default-error.log
  LogLevel  ssl:warn

  SSLEngine on
  SSLCertificateFile      /etc/pki/tls/certs/node01.example.com_fullchain.pem
  SSLCertificateKeyFile   /etc/pki/tls/private/node01.example.com_privkey.pem

  SSLVerifyClient         none

  RewriteEngine On
  RewriteRule ^ - [R=421,L]
</VirtualHost>
Server-wide TLS and HTTP settings: 10-mtg_ssl_hardening.conf
# Example configuration with placeholder names and Red Hat paths.
# Adapt using the Recommended Apache Configuration guide before deployment.
SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite          TLSv1.3 TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_SHA256
SSLCipherSuite          ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-CCM:ECDHE-ECDSA-AES128-CCM:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256
SSLOpenSSLConfCmd       Groups secp256r1:secp384r1:secp521r1:brainpoolP256r1tls13:brainpoolP384r1tls13:brainpoolP512r1tls13:brainpoolP256r1:brainpoolP384r1:brainpoolP512r1

SSLHonorCipherOrder     on

SSLCompression          off
SSLSessionTickets       off
SSLUseStapling          on
SSLStaplingCache        "shmcb:/run/httpd/ssl_stapling(32768)"
SSLStaplingResponderTimeout 5
SSLStaplingReturnResponderErrors off

Protocols               h2 http/1.1

ServerTokens            Prod
ServerSignature         Off
TraceEnable             Off

<Directory "/var/www/html">
    Options -Indexes
</Directory>

Header always set X-Content-Type-Options "nosniff" "expr=-z resp('X-Content-Type-Options')"
Header always set Referrer-Policy "strict-origin-when-cross-origin" "expr=-z resp('Referrer-Policy')"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=()" "expr=-z resp('Permissions-Policy')"
RHEL server-wide SSL settings: ssl.conf
# Example configuration with placeholder names and Red Hat paths.
# Adapt using the Recommended Apache Configuration guide before deployment.
Listen 443 https

SSLPassPhraseDialog     exec:/usr/libexec/httpd-ssl-pass-dialog
SSLSessionCache         shmcb:/run/httpd/sslcache(512000)
SSLSessionCacheTimeout  300
SSLCryptoDevice         builtin
CLM and enrollment protocol virtual host: clm.node01.example.com.conf
# Example configuration with placeholder names and Red Hat paths.
# Adapt using the Recommended Apache Configuration guide before deployment.
<VirtualHost *:80>
  ServerName clm.node01.example.com

  Alias /.well-known/acme-challenge/ /var/www/html/.well-known/acme-challenge/

  <Directory /var/www/html/.well-known/acme-challenge/>
      Options None
      AllowOverride None
      Require all granted
      ForceType text/plain
  </Directory>

  <Directory "/var/www/html">
      Options -Indexes -FollowSymLinks
      AllowOverride None
  </Directory>

  RewriteEngine On

  RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge/
  RewriteCond %{REQUEST_URI} !^/rev-info
  RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L]

  ProxyPreserveHost       On

  ProxyPass               /rev-info http://127.0.0.1:8906/rev-info
  ProxyPassReverse        /rev-info http://127.0.0.1:8906/rev-info
</VirtualHost>

<VirtualHost *:443>
  ServerName clm.node01.example.com

  CustomLog /var/log/httpd/clm.node01.example.com-access.log combined
  ErrorLog  /var/log/httpd/clm.node01.example.com-error.log
  LogLevel  ssl:warn

  SSLEngine on
  SSLCertificateFile      /etc/pki/tls/certs/node01.example.com_fullchain.pem
  SSLCertificateKeyFile   /etc/pki/tls/private/node01.example.com_privkey.pem

  SSLVerifyClient         none

  RewriteEngine On
  RewriteRule ^/?$ https://%{HTTP_HOST}/clm-ui [R=301,L]

  ProxyPreserveHost       On

  ProxyPass               /clm-api http://127.0.0.1:8501/clm-api
  ProxyPassReverse        /clm-api http://127.0.0.1:8501/clm-api

  ProxyPass               /clm-ui http://127.0.0.1:3000/clm-ui
  ProxyPassReverse        /clm-ui http://127.0.0.1:3000/clm-ui

  ProxyPass               /acme http://127.0.0.1:8502/acme
  ProxyPassReverse        /acme http://127.0.0.1:8502/acme

  ProxyPass               /cmp http://127.0.0.1:8504/cmp
  ProxyPassReverse        /cmp http://127.0.0.1:8504/cmp

  ProxyPass               /aec http://127.0.0.1:8506/aec
  ProxyPassReverse        /aec http://127.0.0.1:8506/aec

  ProxyPass               /scep http://127.0.0.1:8507/scep
  ProxyPassReverse        /scep http://127.0.0.1:8507/scep

</VirtualHost>
Keycloak virtual host: auth.node01.example.com.conf
# Example configuration with placeholder names and Red Hat paths.
# Adapt using the Recommended Apache Configuration guide before deployment.
<VirtualHost *:80>
  ServerName auth.node01.example.com

  Alias /.well-known/acme-challenge/ /var/www/html/.well-known/acme-challenge/

  <Directory /var/www/html/.well-known/acme-challenge/>
      Options None
      AllowOverride None
      Require all granted
      ForceType text/plain
  </Directory>

  <Directory "/var/www/html">
      Options -Indexes -FollowSymLinks
      AllowOverride None
  </Directory>

  RewriteEngine On
  RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge/
  RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L]
</VirtualHost>

<VirtualHost *:443>
  ServerName auth.node01.example.com

  CustomLog /var/log/httpd/auth.node01.example.com-access.log combined
  ErrorLog  /var/log/httpd/auth.node01.example.com-error.log
  LogLevel  ssl:warn

  SSLEngine on
  SSLCertificateFile      /etc/pki/tls/certs/node01.example.com_fullchain.pem
  SSLCertificateKeyFile   /etc/pki/tls/private/node01.example.com_privkey.pem

  SSLVerifyClient         optional
  SSLVerifyDepth          2
  SSLCACertificateFile    /etc/pki/tls/certs/trusted-ca.pem

  RequestHeader unset     X-SSL-Client-Verify
  RequestHeader unset     X-SSL-Client-S-DN
  RequestHeader unset     x-ssl-client-cert
  RequestHeader set       X-SSL-Client-Verify "%{SSL_CLIENT_VERIFY}s" "expr=%{SSL_CLIENT_VERIFY} == 'SUCCESS'"
  RequestHeader set       X-SSL-Client-S-DN   "%{SSL_CLIENT_S_DN}s"   "expr=%{SSL_CLIENT_VERIFY} == 'SUCCESS'"
  RequestHeader set       x-ssl-client-cert   "%{SSL_CLIENT_CERT}s"   "expr=%{SSL_CLIENT_VERIFY} == 'SUCCESS'"

  RewriteEngine On
  RewriteRule ^/?$ https://%{HTTP_HOST}/auth [R=301,L]

  ProxyPreserveHost       On

  RequestHeader unset     X-Forwarded-For
  RequestHeader unset     X-Forwarded-Host
  RequestHeader unset     X-Forwarded-Server
  RequestHeader unset     Forwarded
  RequestHeader set       X-Forwarded-Proto "https"
  RequestHeader set       X-Forwarded-Port  "443"

  ProxyPass               /auth/health http://127.0.0.1:9000/auth/health
  ProxyPassReverse        /auth/health http://127.0.0.1:9000/auth/health

  ProxyPass               /auth http://127.0.0.1:8505/auth
  ProxyPassReverse        /auth http://127.0.0.1:8505/auth
</VirtualHost>
EST virtual host: est.node01.example.com.conf
# Example configuration with placeholder names and Red Hat paths.
# Adapt using the Recommended Apache Configuration guide before deployment.
<VirtualHost *:80>
  ServerName est.node01.example.com

  Alias /.well-known/acme-challenge/ /var/www/html/.well-known/acme-challenge/

  <Directory /var/www/html/.well-known/acme-challenge/>
      Options None
      AllowOverride None
      Require all granted
      ForceType text/plain
  </Directory>

  <Directory "/var/www/html">
      Options -Indexes -FollowSymLinks
      AllowOverride None
  </Directory>

  RewriteEngine On

  RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge/
  RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L]
</VirtualHost>

<VirtualHost *:443>
  ServerName est.node01.example.com

  CustomLog /var/log/httpd/est.node01.example.com-access.log combined
  ErrorLog  /var/log/httpd/est.node01.example.com-error.log
  LogLevel  ssl:warn

  SSLEngine on
  SSLCertificateFile      /etc/pki/tls/certs/node01.example.com_fullchain.pem
  SSLCertificateKeyFile   /etc/pki/tls/private/node01.example.com_privkey.pem

  SSLVerifyClient         optional
  SSLVerifyDepth          2
  SSLCACertificateFile    /etc/pki/tls/certs/trusted-ca.pem

  RequestHeader unset     X-SSL-Client-Verify
  RequestHeader unset     X-SSL-Client-S-DN
  RequestHeader unset     x-ssl-client-cert
  RequestHeader set       X-SSL-Client-Verify "%{SSL_CLIENT_VERIFY}s" "expr=%{SSL_CLIENT_VERIFY} == 'SUCCESS'"
  RequestHeader set       X-SSL-Client-S-DN   "%{SSL_CLIENT_S_DN}s"   "expr=%{SSL_CLIENT_VERIFY} == 'SUCCESS'"
  RequestHeader set       x-ssl-client-cert   "%{SSL_CLIENT_CERT}s"   "expr=%{SSL_CLIENT_VERIFY} == 'SUCCESS'"

  ProxyPreserveHost       On

  ProxyPass               /.well-known/est http://127.0.0.1:8503/est/.well-known/est
  ProxyPassReverse        /.well-known/est http://127.0.0.1:8503/est/.well-known/est

  ProxyPass               /est http://127.0.0.1:8503/est
  ProxyPassReverse        /est http://127.0.0.1:8503/est
</VirtualHost>
CARA 3.x virtual host: cara.node01.example.com.conf
# Example configuration with placeholder names and Red Hat paths.
# Adapt using the Recommended Apache Configuration guide before deployment.
# This virtual host applies exclusively to CARA 3.x.
<VirtualHost *:80>
  ServerName cara.node01.example.com

  Alias /.well-known/acme-challenge/ /var/www/html/.well-known/acme-challenge/

  <Directory /var/www/html/.well-known/acme-challenge/>
      Options None
      AllowOverride None
      Require all granted
      ForceType text/plain
  </Directory>

  <Directory "/var/www/html">
      Options -Indexes -FollowSymLinks
      AllowOverride None
  </Directory>

  RewriteEngine On
  RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge/
  RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L]
</VirtualHost>

<VirtualHost *:443>
  ServerName cara.node01.example.com

  CustomLog /var/log/httpd/cara.node01.example.com-access.log combined
  ErrorLog  /var/log/httpd/cara.node01.example.com-error.log
  LogLevel  ssl:warn

  SSLEngine on
  SSLCertificateFile      /etc/pki/tls/certs/node01.example.com_fullchain.pem
  SSLCertificateKeyFile   /etc/pki/tls/private/node01.example.com_privkey.pem

  SSLVerifyClient         none

  RewriteEngine On
  RewriteRule ^/?$ https://%{HTTP_HOST}/cara-admin-ui [R=301,L]

  ProxyPreserveHost       On

  ProxyPass               /cara-ws http://127.0.0.1:8902/cara-ws
  ProxyPassReverse        /cara-ws http://127.0.0.1:8902/cara-ws

  ProxyPass               /cara-admin-ui http://127.0.0.1:3007/cara-admin-ui
  ProxyPassReverse        /cara-admin-ui http://127.0.0.1:3007/cara-admin-ui
</VirtualHost>
KMS virtual host: kms.node01.example.com.conf
# Example configuration with placeholder names and Red Hat paths.
# Adapt using the Recommended Apache Configuration guide before deployment.
<VirtualHost *:80>
  ServerName kms.node01.example.com

  Alias /.well-known/acme-challenge/ /var/www/html/.well-known/acme-challenge/

  <Directory /var/www/html/.well-known/acme-challenge/>
      Options None
      AllowOverride None
      Require all granted
      ForceType text/plain
  </Directory>

  <Directory "/var/www/html">
      Options -Indexes -FollowSymLinks
      AllowOverride None
  </Directory>

  RewriteEngine On

  RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge/
  RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L]
</VirtualHost>

<VirtualHost *:443>
  ServerName kms.node01.example.com

  CustomLog /var/log/httpd/kms.node01.example.com-access.log combined
  ErrorLog  /var/log/httpd/kms.node01.example.com-error.log
  LogLevel  ssl:warn

  SSLEngine on
  SSLCertificateFile      /etc/pki/tls/certs/node01.example.com_fullchain.pem
  SSLCertificateKeyFile   /etc/pki/tls/private/node01.example.com_privkey.pem

  SSLVerifyClient         none

  RewriteEngine On
  RewriteRule ^/?$ https://%{HTTP_HOST}/kms-ui [R=301,L]

  ProxyPreserveHost       On

  ProxyPass               /kms ajp://127.0.0.1:8801/kms secret=<KMS_AJP_SECRET>
  ProxyPassReverse        /kms ajp://127.0.0.1:8801/kms

  ProxyPass               /kms-ui http://127.0.0.1:3005/kms-ui
  ProxyPassReverse        /kms-ui http://127.0.0.1:3005/kms-ui

  ProxyPass               /kms-crypto-api ajp://127.0.0.1:8807/kms-crypto-api secret=<CRYPTO_API_AJP_SECRET>
  ProxyPassReverse        /kms-crypto-api ajp://127.0.0.1:8807/kms-crypto-api

</VirtualHost>

Verification

Run the OS-specific syntax, module and virtual host checks described in the migration guide. Then test every deployed hostname with server certificate verification enabled. For example, replace the example names and CA files below with those for your environment:

openssl s_client -connect clm.node01.example.com:443 \
  -servername clm.node01.example.com -tls1_3 \
  -CAfile server-ca.pem -verify_hostname clm.node01.example.com \
  -verify_return_error </dev/null

curl --cacert server-ca.pem --tlsv1.3 --tls-max 1.3 \
  https://est.node01.example.com/.well-known/est/cacerts

curl --cacert server-ca.pem --tlsv1.3 --tls-max 1.3 \
  --cert client-fullchain.pem --key client-key.pem \
  https://est.node01.example.com/.well-known/est/cacerts

Confirm the negotiated TLS version and certificate verification result. Repeat the connection check with TLS 1.2 and confirm that TLS 1.0 and TLS 1.1 are refused using a client capable of offering them. To test a node before changing DNS, use curl’s --resolve hostname:443:node-ip option, preserving the hostname for SNI and certificate verification.

The cacerts requests check EST reachability with and without a certificate; they do not prove authenticated enrollment. Also perform certificate-authenticated reenrollment and HTTP Basic enrollment with the real client workflows and test Keycloak X.509 login if enabled. Verify the first request on a new connection and subsequent requests, without relying on PHA support in a particular test client.

Check these outcomes:

  • Services configured with none work without a client certificate or certificate selection prompt.

  • Services configured with optional permit anonymous operations allowed by the application and accept trusted client certificates for authenticated operations.

  • A presented untrusted or expired client certificate is rejected.

  • EST accepts HTTP Basic authentication without a client certificate and handles authentication in mtg-est-server.

  • Forged certificate and forwarding headers cannot create an authenticated identity or alter the application’s advertised public URLs.

  • Browser login, APIs and deployed enrollment protocols use the intended names and paths.

  • Existing CRL and OCSP URLs remain reachable over HTTP and ACME HTTP-01 challenges are served without redirection.

  • Unknown hostnames reach the default virtual host rather than an application or distribution welcome page.

  • Renewal installs certificates at the new paths and reloads httpd or apache2 successfully.