Linux servers.

Installing Dual TLS Certificates and Running TLS

Steps Toward The Goal

On earlier pages I showed how to set up a Google Compute Engine virtual machine, run FreeBSD, and set up a basic Apache/PHP web server. Now let's set up HTTPS!

On this page I will:
1: Set up both RSA and ECC key pairs.
2: Obtain Let's Encrypt certificates for both.
3: Set up automatic renewal.

On the final page I will tune the Apache HTTP headers.

Why TLS?

TLS, the successor to SSL, authenticates a web server and provides confidentiality and integrity of the data.

Try Google Cloud Platform and receive $50

In July 2018, Google's Chrome browser began marking HTTP pages as Not Secure. Google had warned the previous year that Chrome would eventually do this. Once Chrome does something, the other browsers quickly follow.

Google also announced back in 2014 that HTTPS already had a slight effect on a page's rank in search results, and the weight would probably increase. In other words, if you don't run HTTPS, you will be ranked lower in search results. Similarly, once Google's search algorithm starts doing something, the other search engines follow.

So, even if a web site is purely informational, you still want to set up TLS.

Public-Key Security

How RSA Works

Asymmetric cryptography, also called public-key cryptography, bases its security on a trapdoor function. That's a function that is easy to compute in one direction but enormously difficult to compute in the opposite direction. RSA, which was developed in the late 1970s, relies on the difficulty of factoring the product of two very large prime numbers.

It is easy to multiply integers, even ones with hundreds of digits, but it is impractically difficult to start with such a product and figure out which two large prime numbers went into it.

Peter Shor's initial publication of his algorithm, 1994 An update to Shor's original paper, 1995 Increasing the speed of Shor's algorithm, 1998

Then people worried: what if someone develops a general-purpose quantum computer? Shor's Algorithm could quickly factor very large numbers if you have such a platform on which to run it.

Around this time, people were starting to use mobile devices for Internet access, but smart phones with fast multi-core CPUs had not yet appeared.

How ECC Works

So, Elliptic Curve Cryptography or ECC suddenly became popular. Its trapdoor function is based on a discrete logarithm, entirely different from RSA's factoring. Analysis by NSA and NIST showed that ECC provides equal security with much smaller keys than RSA, requiring much less computation.

So, there were two advantages for ECC: much higher performance, and expected resistance to sudden obsolescence when quantum computers appear. Certificate authorities began issuing dual certificates for sites: one based on ECC which newer clients would prefer for performance, and RSA as a fall-back.

Since then, cryptographers have discovered that ECC will be slightly more susceptible than RSA to attack by quantum computers. But ECC still has a performance advantage.

Let's Encrypt

Let's Encrypt is a certificate authority founded by the Electronic Frontier Foundation, the Mozilla Foundation, the University of Michigan, Akamai Technologies, and Cisco Corporation.

The Let's Encrypt CA gives you a certificate that's good for 90 days. The short certificate lifetime makes automated renewal important.

FreeBSD
and Linux
Package
Management

ACME, the Automated Certificate Management Environment, is a protocol for interacting with the Let's Encrypt CA. You use the certbot program to carry out the various steps. Install the py37-certbot package to get it.

Preparing to Create Key Pairs and Certificates

First, edit the OpenSSL configuration. Edit the file /etc/ssl/openssl.cnf. Search for the string v3_req and make sure that this line isn't commented out:

req_extensions = v3_req 

Search for the next instance of v3_req, which will be a section header. Again, uncomment it if needed.

[ v3_req ] 

Add or uncomment a following subjectAltName line in that section, and add a tlsfeature line. Then, before the start of the next section, add a new alt_name section in which you define the alternative names you want included in your certificate. The result should be something like the following, my additions are highlighted:

[... many lines deleted ...]

# Extensions to add to any X.509v3 certificate request
[ v3_req ]
# This goes against PKIX guidelines but some CAs do it and some software
# requires this to avoid interpreting an end user certificate as a CA.
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
# Support multiple alternative names.
subjectAltName = @alt_names
# Support OCSP Must-Staple.
tlsfeature = status_request

[ alt_names ]
DNS.1 = www.cromwell-intl.com
DNS.2 = cromwell-intl.com
DNS.3 = ftp.cromwell-intl.com

[... many lines deleted ...]

The old way of supporting OCSP Must-Staple required a cryptic line:
1.3.6.1.5.5.7.1.24 = DER:30:03:02:01:05
See Scott Helm's site for an explanation of the security advantages of OCSP Must-Staple, and the meaning of the old syntax.

Creating the RSA Key Pair and Certificate

You can use the certbot command to create an RSA key pair. But it's unneccesarily confusing — the renew subcommand actually creates a new key pair rather than renewing anything. And, we'll need to use the openssl command to generate the elliptic-curve keys.

So, let's keep it simple.

Now, let's generate a 4096-bit RSA key pair. The result goes into a file named rsa-privkey.pem. The actual file names, including any dotted suffix, don't actually matter. But settle on some pattern that makes sense to you.

# openssl genpkey -algorithm RSA
	-pkeyopt rsa_keygen_bits:4096 \
	-out rsa-privkey.pem 

That file is commonly called the "private key file", although it contains much more — the modulus or public key, the public exponent, the private exponent, both primes, two exponent terms, and a coefficient. Check the results with this command:

# openssl rsa -noout -text \
	-in rsa-privkey.pem | less
RSA Private-Key: (4096 bit, 2 primes)
modulus:
[... 35 lines omitted ...]
publicExponent: 65537 (0x10001)
privateExponent:
[... 35 lines omitted ...]
prime1
[... 18 lines omitted ...]
prime2:
[... 18 lines omitted ...]
exponent1:
[... 18 lines omitted ...]
exponent2:
[... 18 lines omitted ...]
coefficient:
[... 17 lines omitted ...]

Now we'll create a certificate-signing request or CSR. It will ask us for some metadata — two-character country code, state or province, locality, and so on. The result goes into a file named rsa-csr.pem.

# openssl req -new \
	-key rsa-privkey.pem \
	-out rsa-csr.pem

You could automate the process and avoid the interactive questions by defining a long Subject string on the command line. Note that you must escape embedded spaces. Here I'm leaving Organizational Unit empty, just "." for "OU".

# openssl req -new \
	-key rsa-privkey.pem \
	-subj "/C=US/ST=Indiana/L=West\ Lafayette/O=Cromwell\ International/OU=./CN=cromwell-intl.com/"
	-outform pem -out rsa-csr.pem

You can check the results with the following command. Your should see what you intended in the Subject field near the top, your multiple host names in the Subject Alternative Name field, and the TLS Feature status_request.

# openssl req -noout -text \
	-in rsa-csr.pem | less

The Let's Encrypt certbot package creates a directory structure. On FreeBSD it's in /usr/local/etc/letsencrypt, let's not fight with it. It includes a subdirectory accounts where you store your account information. Let's put our private key file and certificate signing request into their intended locations.

# mv rsa-privkey.pem /usr/local/etc/letsencrypt/keys
# mv rsa-csr.pem /usr/local/etc/letsencrypt/csr

We'll get started on the elliptic-curve keys and then come back and ask for the certificates.

Creating the ECC Key Pairs

Now I will create an ECC private key and certificate. You need a reasonably recent version of openssl. See what yours is capable of:

$ openssl ecparam -list_curves | less
Understanding
Elliptic-Curve
Cryptography

I will use elliptic curve P-384, designated secp384r1. That was the strongest elliptic curve included in NSA Suite B cryptography, later renamed the CNSA Suite, and therefore many people interpret that to mean they should not use secp521r1. Browsers support secp384r1, but some of them no longer support secp521r1.

Also note that the NSA Suite B Cryptography and its replacement the Commercial National Security Algorithm Suite specified that Elliptic-Curve Diffie-Hellman and Elliptic-Curve Digital Signature Algorithm (ECDH and ECDSA) with curve P-384, designated secp384r1, are adequate to protect US National Security Systems information up to the Top Secret level. That's good enough for my purposes.

See the U.S. NIST SP 800-57 "Recommendation for Key Management" for the secp384r1 definition, and this comparison of key lengths needed for approximately equal resistance to brute-force attack:

Security
Strength
Symmetric
(3DES, AES)
Asymmetric
(RSA, DSA)
Elliptic
Curve
80 80 1024 160
112 112 2048 224
128 128 3072 256
192 192 7680 384
256 256 15,360 521

Yes, that's 521 in the above table. Earlier versions of the document explicitly said "521". Its revision 5 instead says "512+". US NIST SP 800-56A lists these curves, defined in RFC 4492:

FIPS 186-4
SP 800-56A
TLS / RFC 4492
SP 800-52
IPsec with IKEv2
RFC 5903
Strength
(bits)
P-224 secp224r1 112
P-256 secp256r1 secp256r1 112–128
P-384 secp384r1 secp384r1 112–192
P-521 secp521r1 secp521r1 112–256
K-233 sect233k1 112–128
K-283 sect283k1 112–128
K-409 sect409k1 112–192
K-571 sect571k1 112–256
B-233 sect233r1 112–128
B-283 sect283r1 112–128
B-409 sect409r1 112–192
B-571 sect571r1 112–256
Qualys Browser Test

Qualys, also known as SSL Labs, provides a browser test. Scroll down to the "Protocol Features" section, then within "Protocol Details" look for "Named Groups". On Chrome I see the following. GREASE is an extension intended to detect server compatability issues and drive their repair. It's intentionally not a defined group. See its description in RFC 8701. It is an IETF draft standard. It will be reported as tls_grease_caca or similar, the last four characters hexadecimal in the form *a*a.

Named Groups     tls_grease_caca, x25519, secp256r1, secp384r1

On Firefox I see the following. Notice that Firefox still supports curve secp521r1:

Named Groups     x25519, secp256r1, secp384r1, secp521r1, ffdhe2048, ffdhe3072 

On the browser built into Android on my phone I see the same thing I see on Chrome:

Named Groups     tls_grease_aaaa, x25519, secp256r1, secp384r1 

On lynx I see:

Named Groups     secp256r1, secp384r1, secp521r1, x25519, x448, ffdhe2048, ffdhe3072, ffdhe4096, ffdhe6144, ffdhe8192 

On Safari you see secp521r1 similar to Firefox and Lynx, and GREASE similar to Chrome.

Named Groups     tls_grease_1a1a, x25519, secp256r1, secp384r1, secp521r1 

secp384r1 is the strongest curve supported by all modern browser clients.

My server prefers the order:
ssl_ecdh_curve X25519:secp521r1:secp384r1;
It seems that a fleet of *.search.msn.com indexing bots are about the only clients that use secp384r1.

Let's generate a secp384r1 elliptic-curve key pair.

# openssl ecparam -genkey -name secp384r1 | \
	openssl ec -out ecc-privkey.pem

You can check the results with this command:

# openssl ec -noout -text -in ecc-privkey.pem

Now I can generate the CSR:

# openssl req -new \
	-key ecc-privkey.pem \
	-subj "/C=US/ST=Indiana/L=West\ Lafayette/O=Cromwell\ International/OU=./CN=cromwell-intl.com/"
	-outform pem -out ecc-csr.pem

You can check the results. Your should again see your intended Subject, Alternative Names and status_request.

# openssl req -noout -text -in ecc-csr.pem

Now we'll store those files.

# mv ecc-privkey.pem /usr/local/etc/letsencrypt/keys
# mv ecc-csr.pem /usr/local/etc/letsencrypt/csr

Organizing the Files

The certbot package created subdirectories named archive and live, and a subdirectory within each of those named for my domain.

Some of that was created when I initially experimented with the renew subcommand. Here's how I ended up renaming, duplicating, and repurposing those directories. All of this is under the letsencrypt directory, subdirectories under /usr/local/etc/letsencrypt:

accounts/
My Let's Encrypt account definitions.

csrs/
Certificate Signing Request files.

keys/
Private key files.

ecc-live/cromwell-intl.com/
Elliptic-curve key and certificate files.

rsa-live/cromwell-intl.com/
RSA key and certificate files.

Plus several unused areas:
ecc-archive/
renewal/
renewal-hooks/
rsa-archive/

I copied the private key files into place.

# cp /usr/local/etc/letsencrypt/keys/ecc-privkey.pem \
	/usr/local/etc/letsencrypt/ecc-live/cromwell-intl.com/privkey.pem
# cp /usr/local/etc/letsencrypt/keys/rsa-privkey.pem \
	/usr/local/etc/letsencrypt/rsa-live/cromwell-intl.com/privkey.pem

Now we're ready to create the certificates.

Creating the Certificates

I initially ran the certbot commands and moved files around by hand. Once I had it working, I created the following script that cron runs every Friday morning:

#!/bin/sh

LOGFILE=/root/certbot-output
echo '' > $LOGFILE

for keytype in ecc rsa
do
	echo "$keytype renewal ====================================" >> $LOGFILE
	certbot certonly --non-interactive --webroot \
		-w /usr/local/www/htdocs \
		-d cromwell-intl.com -d www.cromwell-intl.com \
		--email bob.cromwell@comcast.net \
		--csr /usr/local/etc/letsencrypt/csr/${keytype}-csr.pem \
		--agree-tos >> $LOGFILE 2>&1
	echo "Installing files ===============================" >> $LOGFILE
	STORAGE=/usr/local/etc/letsencrypt/${keytype}-live/cromwell-intl.com
	# The certificate itself:
	mv -fv 0000_cert.pem  $STORAGE/cert.pem >> $LOGFILE
	# The signing chain:
	mv -fv 0000_chain.pem $STORAGE/chain.pem >> $LOGFILE
	# The full chain including our certificate:
	mv -fv 0001_chain.pem $STORAGE/fullchain.pem >> $LOGFILE
done

echo "Nginx restart =================================" >> $LOGFILE
pkill nginx ; sleep 3 ; pkill nginx
/usr/local/etc/rc.d/php-fpm stop >> $LOGFILE 2>&1
/usr/local/etc/rc.d/php-fpm start >> $LOGFILE 2>&1
/usr/local/nginx/sbin/nginx -t >> $LOGFILE 2>&1
/usr/local/nginx/sbin/nginx >> $LOGFILE 2>&1

Be careful developing your solution

You can only request 5 duplicate certificates per week. So, develop any scripts with the certificate renewals commented out, until you're pretty sure that it's going to work. See the rate limit documentation for details.

Enabling HTTPS

httpd.conf, the Apache configuration file, contains the following:

# Put these directives at the global level:
LoadModule ssl_module libexec/apache24/mod_ssl.so
Listen 443

# Put these within individual VirtualHost stanzas
# if you are hosting several sites on one server.
<VirtualHost *:443>
    ServerName cromwell-intl.com
    SSLEngine on
    # ECC secp384r1
    SSLCertificateFile "/usr/local/etc/letsencrypt/ecc-live/cromwell-intl.com/fullchain.pem"
    SSLCertificateKeyFile "/usr/local/etc/letsencrypt/ecc-live/cromwell-intl.com/privkey.pem"
    # RSA
    SSLCertificateFile "/usr/local/etc/letsencrypt/rsa-live/cromwell-intl.com/fullchain.pem"
    SSLCertificateKeyFile "/usr/local/etc/letsencrypt/rsa-live/cromwell-intl.com/privkey.pem"
</VirtualHost>

Examining the Certificates

We can use the openssl tool to parse and display the certificates. I use PHP to run the command on the actual file and insert the output here:

# openssl x509 -noout -text -in ecc-live/cromwell-intl.com/cert.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            04:e9:53:64:88:eb:fd:84:9b:63:47:95:a6:3f:39:87:9b:c0
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = US, O = Let's Encrypt, CN = R3
        Validity
            Not Before: Apr  7 07:40:13 2024 GMT
            Not After : Jul  6 07:40:12 2024 GMT
        Subject: CN = cromwell-intl.com
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (384 bit)
                pub:
                    04:d2:ef:ad:da:c1:7e:4d:4b:72:49:cd:e2:d5:94:
                    94:79:4a:81:09:63:cd:7d:44:07:4e:c3:54:48:fc:
                    4b:f8:5c:d3:46:3e:54:a1:9a:e3:03:68:24:39:61:
                    6c:ac:a8:b6:d1:64:fd:2a:b9:af:79:b8:d5:ef:c0:
                    37:b4:82:28:9a:26:2b:0f:24:85:1f:77:28:9f:e1:
                    f3:ca:77:42:20:49:f7:8c:01:f1:aa:05:66:e4:99:
                    46:09:0a:ca:c4:7e:eb
                ASN1 OID: secp384r1
                NIST CURVE: P-384
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Subject Key Identifier: 
                1F:F4:A0:89:41:AA:65:09:49:F9:BF:AF:72:BF:7A:41:01:72:F9:7A
            X509v3 Authority Key Identifier: 
                14:2E:B3:17:B7:58:56:CB:AE:50:09:40:E6:1F:AF:9D:8B:14:C2:C6
            Authority Information Access: 
                OCSP - URI:http://r3.o.lencr.org
                CA Issuers - URI:http://r3.i.lencr.org/
            X509v3 Subject Alternative Name: 
                DNS:alt.cromwell-intl.com, DNS:cromwell-intl.com, DNS:www.cromwell-intl.com
            X509v3 Certificate Policies: 
                Policy: 2.23.140.1.2.1
            CT Precertificate SCTs: 
                Signed Certificate Timestamp:
                    Version   : v1 (0x0)
                    Log ID    : 48:B0:E3:6B:DA:A6:47:34:0F:E5:6A:02:FA:9D:30:EB:
                                1C:52:01:CB:56:DD:2C:81:D9:BB:BF:AB:39:D8:84:73
                    Timestamp : Apr  7 08:40:14.161 2024 GMT
                    Extensions: none
                    Signature : ecdsa-with-SHA256
                                30:46:02:21:00:AA:CD:F2:83:5D:8F:66:04:1E:0E:1D:
                                E1:27:33:84:0B:45:C1:4D:CA:B4:B7:30:D4:B5:A8:DC:
                                81:1A:3E:F8:48:02:21:00:EB:50:6F:8E:3E:64:16:81:
                                0C:59:BB:EE:C6:62:D5:0F:A3:DD:61:22:27:10:52:C2:
                                CC:AB:88:F5:21:0A:FB:AA
                Signed Certificate Timestamp:
                    Version   : v1 (0x0)
                    Log ID    : EE:CD:D0:64:D5:DB:1A:CE:C5:5C:B7:9D:B4:CD:13:A2:
                                32:87:46:7C:BC:EC:DE:C3:51:48:59:46:71:1F:B5:9B
                    Timestamp : Apr  7 08:40:14.163 2024 GMT
                    Extensions: none
                    Signature : ecdsa-with-SHA256
                                30:45:02:21:00:DF:04:CE:73:08:DF:76:4E:36:14:F2:
                                E2:B9:BF:89:49:4D:73:3D:92:B6:C1:B2:0B:89:0A:1E:
                                55:EF:5C:8F:A9:02:20:52:08:35:C0:95:5D:45:63:A8:
                                C4:30:85:4C:E6:94:14:53:07:ED:3E:0F:F6:2F:7D:78:
                                65:33:4F:73:BC:E6:22
            TLS Feature: 
                status_request
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        42:4b:f7:02:f2:01:53:4f:bc:e4:53:0b:3d:ef:ee:f9:5a:5a:
        63:a2:69:dc:4f:eb:e4:38:d7:5f:06:d9:73:1d:8f:d8:2b:ae:
        17:29:d5:36:6c:94:7f:13:12:94:26:09:d1:38:f2:ed:2e:b4:
        2f:7f:85:fe:5e:37:01:d1:5f:00:ab:98:bf:37:8f:a7:1b:6e:
        89:a1:fa:85:d8:0b:99:c6:f3:99:af:d0:9f:ba:ff:7e:ea:7d:
        2e:c9:63:ae:aa:6e:22:87:2b:5a:4c:6b:32:d4:e5:bd:ff:14:
        b4:81:0e:c3:a3:6a:5d:23:dc:2b:17:d6:35:51:06:a2:1a:c3:
        a8:d8:4f:88:2d:88:b4:c6:2b:0d:d4:5f:3d:92:3c:00:ed:8d:
        ea:c5:b9:bc:12:c0:11:8d:81:03:79:2f:c7:7b:ea:1d:e3:bf:
        eb:2c:d4:67:11:6b:a2:31:38:18:35:be:75:0d:5c:05:59:95:
        97:49:1f:b7:eb:5c:cb:cc:63:c1:29:4f:ba:37:fa:13:c7:ce:
        98:b5:4d:0d:b4:d5:8f:71:40:23:9c:14:19:f8:a6:c1:d0:e6:
        2f:9a:02:12:5c:93:7c:7c:4f:d3:15:22:f8:95:01:55:bd:b5:
        c0:e6:82:03:ed:ba:96:08:68:eb:90:b2:dd:f1:ac:90:fa:67:
        bc:81:20:80
# openssl x509 -noout -text -in rsa-live/cromwell-intl.com/cert.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            04:a6:06:8b:69:d9:2b:13:59:e1:7b:6b:6e:c4:62:71:13:2e
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = US, O = Let's Encrypt, CN = R3
        Validity
            Not Before: Apr  7 07:40:17 2024 GMT
            Not After : Jul  6 07:40:16 2024 GMT
        Subject: CN = cromwell-intl.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (4096 bit)
                Modulus:
                    00:af:55:1e:44:f3:ce:fc:53:19:61:e4:b9:c8:ef:
                    90:74:a6:67:ad:64:86:4d:f9:8b:2e:1b:33:0f:6f:
                    69:fc:0f:5f:f4:50:3c:12:17:9d:fd:82:8d:ce:f3:
                    65:31:b2:a0:bd:d8:2a:57:11:0f:6b:c7:9a:a8:c6:
                    1f:ff:e9:e5:63:0b:57:b4:b1:18:2f:d0:7c:36:12:
                    5a:e5:d0:88:34:be:ee:71:7c:e3:81:10:f5:6d:36:
                    da:db:01:f0:4c:1e:bc:de:3c:a2:6b:7e:2b:62:c8:
                    8a:59:c8:6d:46:39:f4:61:6c:f8:05:c5:8f:65:2d:
                    60:f4:c2:94:0e:88:05:d2:82:d7:df:27:ad:cf:20:
                    11:54:75:53:67:b5:b7:b1:b2:05:a1:6f:9c:48:e2:
                    45:35:41:6f:c3:7c:1e:f3:a6:c2:0f:c7:bb:4c:7e:
                    57:11:88:b7:78:2d:f1:4c:67:93:2c:07:58:1f:07:
                    09:90:45:5a:d9:2c:29:4c:0e:4c:45:67:ca:f4:99:
                    59:17:92:82:78:af:61:44:22:fe:92:01:19:73:14:
                    87:7b:93:8b:cb:29:a7:9c:fa:31:e1:10:fa:b9:95:
                    5d:dc:74:81:cf:22:67:1a:72:d1:bd:6c:32:fb:db:
                    17:70:be:61:c0:3e:9e:4c:bb:da:e8:21:54:c4:6b:
                    21:e9:49:e7:d0:3b:b9:b8:ff:b6:ad:84:7f:86:63:
                    86:8e:31:55:2c:0a:3f:0b:cb:cc:b9:2c:01:49:bf:
                    16:27:97:b9:31:67:2d:93:40:2b:51:e7:d2:47:00:
                    11:88:0f:25:a9:8c:bb:96:c9:e0:8a:19:55:f7:f4:
                    2f:db:31:f8:9f:fc:9e:14:e2:62:6f:d8:c7:ea:2b:
                    fd:db:17:a4:9d:7a:4f:d1:0f:88:be:c8:72:c0:be:
                    85:a2:d7:aa:cb:04:1d:41:25:96:3d:58:40:f1:0f:
                    77:54:5d:23:2a:16:b8:3d:b1:b3:06:18:3f:5e:d9:
                    f4:a7:1d:9d:89:42:82:cc:e2:b1:44:26:7f:76:75:
                    15:d8:f0:a1:48:d2:b9:d7:df:d7:c4:79:1d:30:87:
                    11:22:a7:90:86:59:65:04:6d:fa:5c:1a:5b:a1:f9:
                    13:1f:33:53:18:4b:57:31:13:9e:37:56:97:38:8d:
                    2c:6f:ec:7e:a0:1c:3f:6e:63:de:c9:cf:dd:25:04:
                    f7:24:3c:f9:22:98:67:e8:fe:b0:a0:2a:ca:60:47:
                    35:f9:3d:52:a7:65:20:45:b0:f1:51:a0:2d:27:6a:
                    9a:9d:c2:a4:d1:2d:4d:16:68:aa:1c:63:b1:0c:13:
                    0f:0b:16:11:f2:b6:5f:e1:70:73:ca:3c:e1:5a:f0:
                    b5:18:8d
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Subject Key Identifier: 
                93:E4:B4:2C:B7:D2:06:98:09:94:B1:99:F5:81:6F:F4:91:24:D0:19
            X509v3 Authority Key Identifier: 
                14:2E:B3:17:B7:58:56:CB:AE:50:09:40:E6:1F:AF:9D:8B:14:C2:C6
            Authority Information Access: 
                OCSP - URI:http://r3.o.lencr.org
                CA Issuers - URI:http://r3.i.lencr.org/
            X509v3 Subject Alternative Name: 
                DNS:alt.cromwell-intl.com, DNS:cromwell-intl.com, DNS:www.cromwell-intl.com
            X509v3 Certificate Policies: 
                Policy: 2.23.140.1.2.1
            CT Precertificate SCTs: 
                Signed Certificate Timestamp:
                    Version   : v1 (0x0)
                    Log ID    : 3F:17:4B:4F:D7:22:47:58:94:1D:65:1C:84:BE:0D:12:
                                ED:90:37:7F:1F:85:6A:EB:C1:BF:28:85:EC:F8:64:6E
                    Timestamp : Apr  7 08:40:17.709 2024 GMT
                    Extensions: none
                    Signature : ecdsa-with-SHA256
                                30:45:02:20:17:13:9E:26:BD:5E:63:49:28:AD:0F:33:
                                57:F3:68:D4:C9:5D:52:A5:77:88:59:C9:98:FD:7F:9B:
                                B2:00:D7:2D:02:21:00:E7:27:88:F3:54:6A:9C:EC:70:
                                B7:EB:3C:30:34:3B:A8:C5:21:D9:F9:9E:CC:0D:58:20:
                                7A:84:53:BC:D3:6F:14
                Signed Certificate Timestamp:
                    Version   : v1 (0x0)
                    Log ID    : 48:B0:E3:6B:DA:A6:47:34:0F:E5:6A:02:FA:9D:30:EB:
                                1C:52:01:CB:56:DD:2C:81:D9:BB:BF:AB:39:D8:84:73
                    Timestamp : Apr  7 08:40:17.704 2024 GMT
                    Extensions: none
                    Signature : ecdsa-with-SHA256
                                30:44:02:20:45:0F:20:5D:A3:40:88:4B:99:9C:4D:B4:
                                6B:07:53:29:7D:4F:68:54:6A:C4:69:D1:19:74:B4:03:
                                7E:EA:2B:F7:02:20:1C:4F:A8:A9:88:CA:1A:FE:08:01:
                                C0:AF:3B:3A:53:DE:51:9D:1D:CF:58:EE:B0:B2:35:AE:
                                A7:98:CD:6A:5C:3D
            TLS Feature: 
                status_request
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        7b:7b:5a:17:ae:98:09:ab:1e:13:35:d4:44:f6:ba:60:65:5f:
        6c:80:cb:52:c6:6c:66:f8:d2:fb:b3:5a:c6:bb:3d:c0:68:fc:
        24:12:c7:a5:b0:bf:c9:c9:21:47:5e:62:3c:0a:11:2d:0f:2f:
        93:0c:8a:78:06:85:eb:e9:ea:cd:01:06:03:fa:6f:87:97:f0:
        60:5b:ae:a4:e0:fe:56:49:6f:fd:02:b9:d0:48:d8:b9:0e:f1:
        30:5a:be:3f:5c:8d:24:88:cf:38:a5:aa:ac:6e:57:ea:80:a7:
        7a:20:4b:a1:22:df:d7:87:77:af:3b:aa:6c:35:53:64:a8:68:
        68:74:d2:88:6f:2b:5c:2c:0f:9f:71:2a:db:30:ec:50:6b:1b:
        e3:1e:df:f7:15:16:32:8e:3e:0a:9f:e3:59:e5:d5:01:bc:2f:
        90:53:42:e0:5f:4a:f3:a6:31:e5:4a:d6:6e:5e:dc:d3:52:b3:
        a0:6a:49:c5:13:9f:2c:28:5f:23:c5:94:14:21:72:b7:6f:70:
        07:4a:11:59:9d:d3:e6:d4:e8:f1:92:5a:3c:5e:53:32:3f:fa:
        8e:ee:33:a7:4e:44:db:ab:cd:3d:66:52:91:da:0b:39:30:8b:
        72:dc:cd:b6:a0:9e:f6:d1:38:c7:bb:25:99:db:d9:68:f8:03:
        63:e9:c5:2b

Initial Test

Restart Apache and verify that you can connect with both HTTP and HTTPS.

You could run a Qualys server test, but at this point it would probably get a "B" grade at best. We will tune the configuration, but first let's set up redirection.

Redirect to HTTPS and Remove "www."

The goal is to accept all connections, redirecting all of these:
http://cromwell-intl.com/some/path/
http://www.cromwell-intl.com/some/path/
https://www.cromwell-intl.com/some/path/
to this:
https://cromwell-intl.com/some/path/

Add the following to your .htaccess file in the root of the web site. Bold shows additions here. Don't duplicate the RewriteEngine line.

# Do NOT duplicate the following line if it
# already exists earlier in the file.
RewriteEngine on

# Remove "www." and redirect HTTP to HTTPS
# Use a standard variable and a tagged regular expression to
# replace the URL with "https://", the host name, and the
# path minus any leading "www.":
RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]
RewriteRule ^(.*)$ https://%1/$1 [R=301,L]
# If they asked for the non-www name but with HTTP,
# build a new HTTPS URL with the host name and path:
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

# We added the following in an earlier step.
# If the client asks for a non-existent *.html file, rewrite it so
# the bad name isn't passed to the PHP engine causing a "Primary script
# unknown" log entry and a plain "File not found" page.
RewriteCond %{REQUEST_FILENAME} \.html$
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_URI} !-f
RewriteRule (.*) - [H=text/html]

Yes, I did the redirection with the site-specific .htaccess file. I could have instead done it with slightly different syntax in the server-wide httpd.conf configuration file. For the single site, given its size and traffic, I didn't see a big advantage of one method over the other.

Now test all combinations of the redirections: http vs https, www. vs not, and include requests for nonexistent files and directories.

Improving the TLS Configuration

Mozilla Config Generator Apache SSL Howto

Apache has a good SSL/TLS how-to document. Even more useful, Mozilla has a configuration generator. You select your server, its version, and your OpenSSL versions, and then you select the security profile.

Which security profile should you use? It depends...

Let's say you're setting up a server for use within your organization, and you have full control of the desktop systems and any portable laptops that could be used to connect in from outside. I recommend the strictest "Modern" profile for that case. All your client machines will need to be fairly current, but that should already be the case.

However, let's say that you want to be open to most all clients from the public. That's my situation. I have ads on my site. While it would be nice if everyone used up-to-date operating systems and browsers, I don't want to block or even inconvenience people with somewhat outdated platforms.

I used the "Intermediate" profile as a starting point. Here is what I added toward the end of the httpd.conf configuration file, before and outside the VirtualHost stanza, so it will apply to all virtually hosted web sites I eventually set up on this server. The cipher suite is usually one enormously long line with no whitespace. That's hard to read and much harder to modify, so I prefer to split it across multiple lines. Just make sure each backslash is the last character on its line.

# TLS only, no SSL
SSLProtocol all -SSLv2 -SSLv3
# Specify ciphers in a preferred order.  I reordered what the configuration
# generator gave me, putting better ciphers first.
SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:\
ECDHE-RSA-CHACHA20-POLY1305:\
ECDHE-ECDSA-AES256-GCM-SHA384:\
ECDHE-RSA-AES256-GCM-SHA384:\
DHE-RSA-AES256-GCM-SHA384:\
ECDHE-ECDSA-AES128-GCM-SHA256:\
ECDHE-RSA-AES128-GCM-SHA256:\
DHE-RSA-AES128-GCM-SHA256:\
ECDHE-ECDSA-AES256-SHA384:\
ECDHE-ECDSA-AES128-SHA256:\
ECDHE-ECDSA-AES256-SHA:\
ECDHE-ECDSA-AES128-SHA:\
ECDHE-RSA-AES256-SHA384:\
ECDHE-RSA-AES128-SHA256:\
ECDHE-RSA-AES256-SHA:\
ECDHE-RSA-AES128-SHA:\
DHE-RSA-AES256-SHA256:\
DHE-RSA-AES128-SHA256:\
DHE-RSA-AES256-SHA:\
DHE-RSA-AES128-SHA:\
AES256-GCM-SHA384:\
AES128-GCM-SHA256:\
AES256-SHA256:\
AES128-SHA256:\
AES256-SHA:\
AES128-SHA:\
ECDHE-ECDSA-DES-CBC3-SHA:\
ECDHE-RSA-DES-CBC3-SHA:\
EDH-RSA-DES-CBC3-SHA:\
DES-CBC3-SHA:\
!DSS

SSLHonorCipherOrder     on
# Disable compression and session tickets
SSLCompression          off
SSLSessionTickets       off
# Enable OCSP Stapling
LoadModule socache_shmcb_module libexec/apache24/mod_socache_shmcb.so
SSLUseStapling On
SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"
# Enable session resumption (caching).
# However do not use large values.  Large advertisers like
# Google and Facebook use that to track users.  Your site will
# look suspicious if it does this.  See, for example:
#   https://www.zdnet.com/article/advertisers-can-track-users-across-the-internet-via-tls-session-resumption/
# 300 seconds (5 minutes) seems reasonable.  That's actually the
# default, no need to specify that, but here's the syntax.
SSLSessionCache "shmcb:logs/ssl_scache"
SSLSessionCacheTimeout 300
# Insist on HSTS or HTTP Strict Transport Security
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

And with that, an A+ evaluation from the Qualys analyzer:

Screenshot of Qualys SSL Labs A+ analysis of a HTTPS web server
More cautious configuration with Nginx

The server supports 3DES as a fallback position if none of the stronger ciphers are supported. That allows connections from IE 8 on XP. While 3DES is flagged in the report as weak, at least at the time that I did this it did not lower the score. Unlike RC4, which capped the grade at B at the time.

DNS CAA or Certification Authority Authorization provides a way to indicate in a DNS record just who is allowed to be a CA for the site. See this Qualys blog for details on what CAA is, and how the CA/Browser Forum has mandated its use.

Google Domains did not yet support CAA records when I started this project in July 2017, as you can see below. Google Cloud DNS, a different product, did.

Screenshot of Google Domains dashboard showing available record types.

By February 2018, they had added CAA records:

Screenshot of Google Domains dashboard with CAA records available.

Let's test it:

$ dig @localhost cromwell-intl.com CAA

; <<>> DiG 9.10.6-P1 <<>> @localhost cromwell-intl.com CAA
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 26186
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 4, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;cromwell-intl.com.		IN	CAA

;; ANSWER SECTION:
cromwell-intl.com.	3570	IN	CAA	128 issue "letsencrypt.org"

;; AUTHORITY SECTION:
cromwell-intl.com.	78163	IN	NS	ns-cloud-c1.googledomains.com.
cromwell-intl.com.	78163	IN	NS	ns-cloud-c4.googledomains.com.
cromwell-intl.com.	78163	IN	NS	ns-cloud-c3.googledomains.com.
cromwell-intl.com.	78163	IN	NS	ns-cloud-c2.googledomains.com.

;; Query time: 0 msec
;; SERVER: ::1#53(::1)
;; WHEN: Wed Feb 14 14:44:24 EST 2018
;; MSG SIZE  rcvd: 198 

What About SMTP/S?

To test your SMTP/S server, use the excellent CheckTLS SMTP/S Test.

Final Step

I was very happy with this result! There are just a few more things to do.

Proceed to the final step to see how to tune the HTTP(S) headers.

Final step: HTTPS Headers