LibreLAMP Apache Configuration


Some people are under the impression that properly setting up a secure Apache web server is a difficult task that requires a lot of skill. If you are under that impression and you have a lot of money and you are willing to pay me to do it for you, then yes, it is an extremely difficult task.

For everyone else, as long as you have a basic understand of the UN*X shell, it really is not that difficult. This page gives basic instructions for completing the task.

I do recommend that you read the ‘SSL/TLS Deployment Best Practices’ guide that is available at

This document (not that linked document) assumes that you are running LibreLAMP on RHEL/CentOS 7 with the mod_ssl package installed.

TLS Private Key and Certificate Generation

A TLS web server works on the principles of Public-key Cryptography.

With Public-key Cryptography, two keys exist. One key has a secret and is kept private, and the other key is a public key generated from that secret. You can always re-create the public key from the private key, but the function is one way, you can not figure out the private key from the public key.

When used just for authentication of a message and not encryption, a signature is created using the private key and attached to the message. The recipient can then use the public key of the sender to verify from the signature that the message has not been altered since it was signed by the private key. It also verifies it was sent by someone who has access to the private key. For signatures you can use RSA key pairs but it is more efficient to use ECDSA key pairs.

When used for encryption, one method is the sender of the message uses the public key provided by the recipient to encrypt the message. This is called asymmetric encryption. Once encrypted, unless the cipher is broken it can only be decrypted using the private key of the recipient. Each person has their own secret key and do not know what the secret is for the other person. This is what PGP and S/MIME encryption in e-mail encryption do. The private keys use the RSA algorithm, ECDSA are only for signatures, they can not be used for encryption.

TLS encryption does not need to be asymmetric. With TLS the public key of the server is used as a signature to prove to the client that the server is who it says it is, or at least has access to the private key that corresponds with the public key in the certificate.

The server and client then use either ECDHE or DHE key exchange and negotiate a fresh shared secret both the client and server know for the session. This shared secret will then be used for symmetric encryption using the chosen symmetric cipher suite. Once the session is over, the shared secret is discarded, a new shared secret will be negotiated for the next session. This is called Forward Secrecy. Forward Secrecy is required in TLS 1.3 and highly recommended when used with TLS 1.2. It is called Forward Secrecy because the shared secret negotiated between the client and server is not derived from the server private key, the server private key only established the authenticity of the server. If a hacker logs an encrypted session and then later obtains the server private key, that private key will not help the hacker decrypt the session.

To implement a TLS server, first we must generate a private key. From that private key, we have to generate a Certificate Signing Request. This CSR will contain the public key associated with the private key, as well as some meta-data about the web site, such as the domain name and the company.

The generated CSR is then sent to a Certificate Authority. The Certificate Authority does some verification that the owner of the domain(s) listed in the CSR is in fact the person sending the CSR. If they are satisfied, the Certificate Authority signs it themselves using their own private key. Any browser that trusts that particular Certificate Authority will then trust the certificate for the website is valid, and allow a TLS connection.

Generate the TLS Private Key

There are two different types of private keys you can generate: ECDSA and RSA.

Previously I recommended RSA keys for most users, noting the ECDSA is the future. Now in 2019, I recommend ECDSA. The future is here.

There is one exception. When scraping OpenGraph metadata, Tumblr is not able to scrape it from websites that use ECDSA certs. All other social media sites I am aware of can, but it seems Tumblr has a really outdated back-end they do not care to update.

Tumblr has, however, in my opinion become irrelevant. Do not worry about catering to it, it is on its way out. However if sharing links on Tumblr is an important marketing tool, then use RSA certs.

When using an ECDSA certificate, I recommend using the secp384r1 curve. When using an RSA certificate, I recommend either 3072-bit or 4096-bit. The latter is stronger but requires more computational power. Reality is even 2048-bit (the minimum) is strong enough.

The RSA instructions here will be for 4096-bit. Feel free to adapt them to 3072-bit or even 2048-bit if you want to be nice to the battery life of your mobile users, but since the certificate is only used for authentication, it probably does not even measurably matter for that.

It is possible to use both certs but I caution against doing so. My personal experience is that some clients that are not capable of using an ECDSA certificate also fail to work with servers that provide both ECDSA and RSA certificates. The back-end content scraper Tumblr uses is one such example. For that reason, I can not recommend dual-certificate configurations. Just use an RSA certificate if you need to support clients not capable of dealing with ECDSA, otherwise just use ECDSA.

On RHEL/CentOS 7 systems, private keys are traditionally kept in the directory /etc/pki/tls/private/.

To generate a secp384r1 ECDSA key in LibreLAMP:

pushd /etc/pki/tls/private
umask 0277
/usr/bin/libressl ecparam -name secp384r1 -genkey -out
umask ${DUMASK}

To generate a 4096-bit RSA key in LibreLAMP:

pushd /etc/pki/tls/private
umask 0277
/usr/bin/libressl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out
umask ${DUMASK}

The purpose behind temporarily changing the umask is to make sure that from the start, only the root user can read the file. Obviously use your domain name instead of and use the current YYYYMMDD instead of 20210410.

Having the date of generation in the filename makes it easy to see when a private key is over a year old. It is best practice to regenerate any private keys that are over a year old, just in case it was compromised without you being aware.

Generate a Certificate Signing Request

A Certificate Signing Request is what you send to a Certificate Authority to obtain a signed certificate. You do not send them your private key, you do not send anyone your private key. Private means private. The CSR is what you send the certificate authority. I emphasize this because I have seen Certificate Authorities before that give you the option of sending them your private key. It breaks my heart when I see something that wrong.

When creating a CSR you will be asked several questions. Most of them are self-explanatory. A few of them are not.

For Organization Name (eg, company) — if this is not for a company website, just use the name of the website.

For Organizational Unit Name (eg, section) — you can just leave that blank.

For Common Name — be sure to use the Fully Qualified Domain Name that the certificate is being issued for (e.g. except when the FQDN begins with www. — then leave the starting www. off of the Common Name field.

For Email Address — you can leave that blank but I usually use the same e-mail address I used when registering the domain.

For A challenge password — it is very important that you leave that blank, or you will need to enter it every time you start the web-server.

Okay, now that those fields have been clarified, here is how to generate a CSR in LibreLAMP:

pushd /etc/pki/tls/csr
/usr/bin/libressl req -new -key ../private/ \

After answering the questions, the CSR will have been created. The contents of the file will look something like this:

(Screen Readers: skip past long base 64 encoded output text)


“But there’s way too much information to decode the Matrix. You get used to it. I don’t even see the code. All I see is blonde, brunette, redhead.” — Cypher

Certificate Types

Now that we have a private key and a CSR generated from that key, we can get a signed certificate. There are four types of signed certificates:

  • Self-Signed
  • Domain Validation (DV)
  • Organization Validation (OV)
  • Extended Validation (EV)

Do not let some slick salesman con you into thinking the more expensive certificates provide better encryption. They do not. The quality of the encryption is strictly determined by your private key and the cipher suite negotiated between the server and the client, the certificate has absolutely nothing to do with the actual encryption.

What a valid certificate does is offer the user a reason why they should trust that you are who say you are. It gives the browser (and the user) some degree of confidence that a MITM attack is not taking place.

A self-signed certificate does not meet that purpose and should not be used on a web server intended for public access. This is a common flaw I see with web hosting companies, by the way. They often use self-signed certificates for pages that users are suppose to use to administer their accounts. If a web-hosting service does not understand the security implications of a self-signed certificate then they should not be trusted to keep the hosts secure, it is that simple.

One of the major problems with a self-signed certificate is that there is not a mechanism by which a browser can determine if the certificate has been revoked. If a hacker manages to get the private key, the hacker can pull off a MITM attack and any browser that accepted the certificate will continue to accept it because it can not effectively be revoked.

Do not use self-signed certificates on public web servers. Just don’t.

With a DV certificate, a Certificate Authority validates that the CSR is coming from an administrator of the domain. This often is done by sending an e-mail to an administrative e-mail address at the domain (e.g. or to the e-mail address in the WHOIS record, if available. It also can be done by DNS checks or other means.

With an OV certificate, the checking a Certificate Authority does is a little more extensive before they issue the certificate. They validate that the website actually is associated with the organization named in the certificate and that the CSR is coming from an appropriate person in the organization.

On the surface that sounds better, but in reality it just costs more. The browser does not care and the user only knows if they actually look at the certificate. Almost no one ever does.

With an EV certificate, extensive validation is done before the Certificate Authority will issue the certificate. If your website is a financial institution or other high target for MITM attacks or phishing attacks, then many users will expect you to have an EV certificate.

Many modern web browsers will give the user a visual indication that the web site has an EV certificate. Typically this is done by placing the company name and country in green letters between the lock and url in the browser bar:

[Image showing the green company name and country displayed between the lock and URL]

Nutshell: Use a DV certificate unless your website is the kind of website likely to be targeted for MITM attacks or phishing attacks. Then spend the extra money for an EV certificate.

Certificate Authority

Traditionally, all Certificate Authorities were commercial. You paid them money and sent them your CSR. They (hopefully) verified what needed to be verified, and issued you a signed certificate.

Let’s Encrypt revolutionized how CA signed certificates may be obtained.

At this point in time, unless your business genuinely needs an EV certificate, I would recommend using them.

The disadvantages of Let’s Encrypt:

  1. They only issue DV certificates
    • For most of the Internet, DV is the right choice.
  2. The certificates expire after 90 days
    • This is only an issue if you use Key Pinning as 2FA for the client and can not automate renewal.

For purchasing a signed TLS certificate (and for domain name registration), I highly highly recommend Namecheap.

Not only is their customer service the best I have ever experienced with any registrar, but they will save you money. Most places that claim to save me money have pathetic customer service, that is not the case with Namecheap. Their customer service is exceptional.

On , a PositiveSSL certificate purchased from Comodo was priced at $49.00 (US). The same certificate purchased from Namecheap, $9.00 (US).

Very often, companies like Comodo actually prefer a re-seller like Namecheap handle the customer service, hence the price difference.

Unless you need an EV certificate, Comodo PositiveSSL is the certificate I recommend. It is inexpensive, and they automatically add the www sub-domain to the domains the certificate is valid for.

Comodo PositiveSSL at Namecheap

Let’s Encrypt

Instructions to come.

Certificate Installation

Your Certificate Authority should have e-mailed you a zip archive containing your signed certificate as well as supporting certificates. Nothing in that zip archive needs to be kept secret, the Apache web server will send them to any client that connects. The zip archive is probably named something like

Upload the bundle to your web server and put it in the /etc/pki/tls/cert_bundle/ directory. I like to rename it to the same scheme I use for the private key and the CSR, and then unpack it:

pushd /etc/pki/tls/cert_bundle

In the case of a Comodo PositiveSSL bundle, this results in two files:

  • example_org.crt

The last one, example_org.crt, is the actual certificate for the site. Move it into /etc/pki/tls/certs/ and rename it to match the same scheme and date stamp as your private key:

mv example_org.crt
mv ../certs/

The first one, with the name bundle in it, is what is sometimes called the Certificate Chain. I prefer to call it the Certificate Authority Bundle. Rename it to the same scheme and date as your private key but noting that it is the bundle:

mv ../certs/

You now have all three TLS related files in place that are needed to configure Apache:

  1. /etc/pki/tls/certs/
  2. /etc/pki/tls/private/
  3. /etc/pki/tls/certs/

Basic Apache TLS Configuration

A fresh install of the Apache web server in LibreLAMP is virtually identical to a fresh install of the Apache web server in RHEL/CentOS 7.

As such, you could just put your web pages in /var/www/html/ but I highly discourage that practice.

The best thing to do, in my opinion, is to create Name Based Virtual Hosts for every domain name Apache will be serving. Each domain should have its own configuration file in /etc/httpd/conf.d/ making it much easier to migrate a configuration from one server to another.

For these instructions, we will continue with the use of and create a fresh Apache Name Based Virtual Host configuration file for that domain called /etc/httpd/conf.d/

Create the Apache Webroot

As the root user:

pushd /srv
mkdir -p
chown -R alice:alice

That gives the user alice and the group alice ownership of the /srv/ directory so that root access is not needed to put content where it will be served.

The directory /srv/ is the Apache web root we will use for the domain.

The directory /srv/ is a directory outside the Apache web root where PHP include files can go, such as database authentication files, that should never be where the web server can accidentally serve them in plain text.

Redirect Virtual Hosts

When running a secure web server, it is considered bad practice to also serve content on the same domain without encryption. So to begin our Apache configuration, we will start with Port 80 redirects to the secure protocol.

For the purpose of brevity, we will only cover IPv4 hosts. However I highly encourage you to run a dual-stack server that supports IPv6 as well.

We will be using as the domain we serve from, redirecting client requests to to Of course you are free to do the reverse on your server if you prefer the reverse.

The start of our /etc/httpd/conf.d/ file:

#Port 80 Redirect

Redirect permanent /

Redirect permanent /

The Redirect permanent directive tells Apache to send a 301 HTTP redirect header. You can see this with the curl utility:

[alice@localhost ~]$ curl -I
HTTP/1.1 301 Moved Permanently
Date: Wed, 07 Feb 2018 17:38:12 GMT
Server: Apache/2.4.29 (LibreLAMP) LibreSSL/2.6.4 PHP/7.1.14
Content-Type: text/html; charset=iso-8859-1

[alice@localhost ~]$

Additionally, we need a redirect on port 443 to redirect client requests to to

Since the request will use TLS, we will need to turn on the SSLEngine for this virtual host, and tell it where the key and certificate files are located.

There are four directives we need to turn on TLS:

SSLEngine on
This directive turns the TLS engine on.
This directive tells Apache where the signed certificate is located.
This directive tells Apache where the private key associated with the certificate is located.
This directive tells Apache where the Certificate Authority bundle certs are located.
# Port 443 Redirect

Redirect permanent /
SSLEngine on
SSLCertificateFile            /etc/pki/tls/certs/
SSLCertificateKeyFile       /etc/pki/tls/private/
SSLCACertificateFile      /etc/pki/tls/certs/

This is why is important for the certificate to be valid for the domain both with and without the www. prefix. The redirect from one to the other needs to validate in the certificate as well, or else the user will get a scary message about a server mismatch.

As you can see, with virtual hosts that only redirect, they really do not need very much. You do not even really need to add log file directives. For the port 80 redirects, you just need the ServerName and Redirect permanent directives. For port 443 you just need to add the the SSL* directives.

Live Server Virtual Host

With the Virtual Hosts that redirect out of the way, now we can configure the Virtual Host they redirect to:

#Live Host

DocumentRoot "/srv/"
SSLEngine on
SSLCertificateKeyFile       /etc/pki/tls/private/
SSLCACertificateFile      /etc/pki/tls/certs/
# private key
SSLCertificateFile            /etc/pki/tls/certs/
ErrorLog logs/
CustomLog logs/ combined

The DocumentRoot directive points to the /srv/ directory we created earlier. We need to tell Apache what it is allowed to do with that directory:

<Directory "/srv/">
  Options FollowSymlinks
  AllowOverride All
  Require all granted
  php_value include_path "/srv/"

I prefer to initially set the Options directive for a directory to just FollowSymlinks. That actually is the default value in Apache 2.4 if it is left out, but earlier versions of Apache defaulted to All if it was left out, which was not very secure. Even though I am specifying the default, I think that is better as it may change again in the future resulting in unintended results when upgrading Apache.

For other options, see the Options Directive in the Apache manual.

The AllowOverride directive specifies what a .htaccess file in within the web root is allowed to do. When setting up a new server I prefer to set it to All so that I have some flexibility without needing to frequently restart the web server. It can be locked down later when the needs of the host are better known.

The Require all granted directive tells Apache that it is okay to serve the contents of the directory to any requesting client.

The php_value directive should only be used on servers that have PHP enabled.

It sets the initial PHP include path for scripts served from that directory to something sane and secure. I do not personally like the common practice of including the current directory in the default PHP include path.

With any luck, your Apache server should now be properly configured to serve content securely over a TLS connection.

HTTP Strict Transport Security

HTTP Strict Transport Security (abbreviated HSTS) is a mechanism by which your web server can tell web browsers that they should always use HTTPS when connecting to your server.

When running a web server, you should have all requests to Port 80 redirect to the TLS enabled secure server on Port 443 but that is not good enough by itself.

Let us say you own the domain You have requests to Port 80 redirect to port 443 but you do not send the HSTS header.

You have a customer named Billy Joe. Billy Joe is a happy camper, loves your bank, he is a really nice guy.

A hacker named Dirnt creates a phishing website,, that he wants to use to steal Billy Joe’s login credentials.

Dirnt knows that Billy Joe always looks carefully at the domain name in any e-mail, so what Dirnt does is send Billy Joe an e-mail pretending to be from his bank telling him there is an issue that needs his attention. The e-mail contains a hyperlink to

The domain is correct, but it goes to Port 80 and not Port 443. Your web server at will redirect Billy Joe to the secure Port 443, as it should, but the redirect itself is not sent over a secure connection because the request came over Port 80.

This allows Dirnt to easily pull a MITM attack on the un-secure plain text redirect, changing it to instead redirect Billy Joe to the server controlled by Dirnt located at

Now Billy Joe’s Day is Blue because Dirnt stole all his Green and now he has to move back to Rodeo Boredeo.

If the real server had made a practice of sending the HSTS header, then from a previous visit Billy Joe’s browser would know to only use a secure connection with that domain and would have changed the insecure request to the secure request from the start, foiling Dirnt’s evil plan so the nice guy doesn’t finish last.

The point is every secure server should implement HSTS. It protects the users.

Sending the HSTS Header

Add the following directive to the beginning of your Apache virtual host configurations:

Header always set Strict-Transport-Security "max-age=63072000; preload"

If every single sub-domain also uses TLS then you can indicate it by using the following directive instead:

Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"

The max-age is in seconds, 730 days in the example above. That tells the browser how long to remember that any requests to the domain should use TLS.

I personally only add the directive to the virtual hosts running on Port 443. Clients that try to connect to Port 80 will get the header as soon as they are redirected, and if there is already a MITM attack taking place on Port 80 redirects, the attack would just filter that header out. So there is really no functional benefit to adding the header to Port 80 virtual hosts.


Without proper configuration, the cipher suite used by the TLS connection may not be the best choice for proper security.

Without proper configuration, it may be possible for a MITM attack to influence the selected cipher used, resulting in a cipher that is easier for the attacker to break. That exact kind of attack has happened in the past.

It is also important that all cipher suites use Forward Secrecy (sometimes called Perfect Forward Secrecy). Forward Secrecy makes it very difficult for a session key to be compromised if in the future one of the long-term keys is compromised.

This is important because even though TLS traffic is encrypted, it can still be logged. We know that the U.S. government NSA has done this in hopes of being able to break the encryption in the future, and they probably are not the only one. Forward Secrecy removes one of the methods such an attacker could use if they managed to capture one of the long-term encryption keys. A properly configured secure web server will only allow cipher suites that properly support Forward Secrecy.

Selecting the proper cipher suites can seem like VooDoo. This section is intended to clarify how the process was done for my own needs, and the resulting configuration should be a good general configuration that you can use.

Cipher suite selection is specific to the virtual host configuration. Each virtual host that runs on port 443 (the SSL/TLS port) needs to have the allowed ciphers specified to really be secure.

I generally place it directly after the SSLEngine on directive:

SSLEngine On
SSLHonorCipherOrder on
SSLCipherSuite "cipher-spec1 cipher-spec2 !cipher-spec3"

The SSLHonorCipherOrder on is important, or the cipher selected may not be the preferred cipher to use for best security and performance.

A cipher-spec is a group of parameters that ciphers are matched against. The parameters are separated by a + delimiter.

For example EECDH+aRSA+AESGCM is a single cipher-spec with three parameters.

All ciphers that match the cipher-spec will be added to the list of ciphers that can be used unless the specified cipher is also in a list of ciphers to exclude.

Ciphers to exclude are put at the end of the SSLCipherSuite directive, and use the same cipher-spec format but the string is preceded by a ! operator.

The mod_ssl package in LibreLAMP always excludes all ciphers that match the following cipher-spec directives automatically:


Those automatic exclusions give protection against known insecure cipher suites when the system administrator makes a configuration mistake (or fails to make any configuration) but they are not a substitute for a proper SSLCipherSuite directive.

To properly configure the SSLCipherSuite directive, we must select the parameters carefully and logically.

Forward Secrecy

For forward secrecy, ciphers supporting either ECDHE key echange or DHE key exhange must be used.

The ciphers that use RSA key exchange should not be used unless you absolutely must support very old clients that do not have forward secrecy support. Those old clients are no longer maintained by their vendor and no longer receive security updates, so supporting them is not secure anyway.

Only ECDHE and DHE key exchange ciphers should be allowed on a secure server.

The TLS 1.3 specification only allows ciphers that support Forward Secrecy, the ECDHE and DHE ciphers. We should not wait to implement that restriction ourselves.

Between the two, ECDHE is superior for a variety of reasons. ECDHE ciphers are much faster, and are not vulnerable to the Logjam Attack. DHE ciphers should only be used for compatibility with clients that do not support ECDHE and if used should be specified at the end of the cipher list.

The cipher-spec parameter to specify ECDHE capable cipher suites is EECDH. The cipher-spec parameter to specify DHE capable cipher suites is EDH.

Private Key Types

There are two types of private key certificates that are suitable for use on a TLS capable web server:

  • >= 2048 bit RSA
  • >= 256 bit ECDSA

ECDSA is the superior of the two and is the future, but a very few clients do not support it. All clients currently support RSA.

Your choice in Certificate Authorities is also currently more limited with ECDSA.

My personal preference on my servers is to just use ECDSA unless it is a website I want to promote via sharing on Tumblr. Tumblr will make use of OpenGraph data in a website but it seems to be using a crusty old version of libcurl that breaks with ECDSA certs, so to share a site on Tumblr and have the OpenGraph data made available, one still needs to use RSA certificates.

The parameter to specify ECDSA capable cipher suites is ECDSA. The parameter to specify RSA capable cipher suites is aRSA. Unless you are using both types of certificates, you do not need to worry about specifying which, the server will only select ciphers that match your certificate.

When you are using both, you should specify that ECDSA capable ciphers are used for ECDHE key exchange.

AEAD Ciphers

AEAD is a mode of operation where the cipher encrypts the confidential data and provides authentication for it during the same operation that it adds non-encrypted associated data to the content. It does this with a simple programming interface.

This is important, when encryption fails very often the failure is because the encryption was bypassed rather than actually broken. With an easier to use programming interface, it is far less likely for there to be bugs in the cipher implementation that allow the encryption to be bypassed.

This is so important that it appears the TLS 1.3 specification will only allow AEAD ciphers for TLS 1.3 connections.

The AEAD ciphers in LibreLAMP are the ciphers that use ChaCha with POLY1305 and AES with GCM.

All other things being equal, AEAD ciphers should be given precedence in the cipher order.

With those concepts in mind, we can now create our SSLCipherSuite directive based on the level of TLS and client support we wish to achieve.

TLS 1.3 Support

At this time LibreSSL does not yet support TLS 1.3. The reason, the developers believed it was better to wait until the draft was finalized before they started implementing it. The draft was only recently finalized.

I believe the developers made the right decision. TLS 1.2 when properly configured has no known attack vectors that are fixed by TLS 1.3 so while it will be nice to have, it is not urgent. It will be here soon.

TLS 1.2 Support

To properly cover all (or at least most) TLS 1.2 clients, we need to take the following parameter combinations into consideration:

  • ChaCha 20 (CHACHA20_POLY1305_SHA256)
  • AES 256 with GCM (AES_256_GCM_SHA384)
  • AES 256 with CBC SHA384 (AES_256_CBC_SHA384)
  • AES 128 with GCM (AES_128_GCM_SHA256)
  • AES 128 with CBC SHA256 (AES_128_CBC_SHA256)

For a strict setup that only uses 256-bit ciphers, you can safely leave the last two off. A few deprecated browsers will not be able to connect but the vast majority of browsers will still be able to connect.

It is my opinion that ChaCha 20 should be first in server preference. It is an AEAD cipher, and for mobile devices which often (always?) do not have hardware acceleration for AES-NI, it is faster than AES.

The 256-bit AES should follow, with preference for GCM as it is an AEAD cipher.

If you want to support the few clients that need 128 bit ciphers, they should come last again with the GCM before the CBC cipher.

Thus our SSLCipherSuite directive would look like this:


On a server with an RSA private key that directive would expand to the list of the following 5 ciphers:

  1. TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca8)
  2. TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
  3. TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 (0xc028)
  4. TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
  5. TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (0xc027)

That short list covers every TLS 1.2 client I am personally aware of.

TLS 1.0 Support

As of 2019 I can no longer condone supporting TLS 1.0 or TLS 1.1.

The only reason to continue supporting them is if your boss tells you that you are fired if you do not support them. Eating is nice.

To support them, remove the -SHA from the end of the cipher-spec.

That will add any ECDHE AES capable ciphers not already listed. The added ciphers:

  1. TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (0xc014)
  2. TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013)

Our SSLCipherSuite now looks like:


That directive gives sane cipher selection for TLS 1.2 clients and for most TLS 1.0 clients.

DHE Cipher Support

In 2019 there is no good reason to support DHE ciphers on a web server. Do not do it.

Again, if it is a matter of eating, you can add EDH+AES256 to the end of the cipher spec. But if your boss demands DHE capable ciphers in 2019, look for another job. Seriously. The only clients that need them are deprecated and should not be considered even remotely secure.

The SSLCipherSuite directive now would look like this:


The following three ciphers were added:

  1. TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 (0x9f)
  2. TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 (0x6b)
  3. TLS_DHE_RSA_WITH_AES_256_CBC_SHA (0x39)

In modern Apache, for the DHE key exchange ciphers just added, Apache will default to using predefined DH parameters corresponding to the number of bits in the RSA certificate.

That means with a 2048-bit RSA certificate, by default you will be using 2048-bit DH parameters from a well-known group used on a lot of different servers.

Apache as packages by LibreLAMP has been patched to default to 3072-bit DH parameters when custom DH parameters are not specified.

Optional Paranoid Tightening

The first two ciphers added by the EDH+AES256 cipher-spec allow for potential ECDHE to DHE key exchange downgrade attack with TLS 1.2 clients. It is very unlikely but their very presence makes it possible.

You can exclude those two ciphers by adding the following to the end of your SSLCipherSuite:


Those clients might still be vulnerable to a downgrade attack to the third cipher, the cipher we want, but that third cipher only uses an SHA1 hash so some TLS 1.2 clients that support the first two already do not support the third and thus are only potentially vulnerable to a downgrade attack to the first two.

Adding the exclusions is paranoid, but the presence of those two ciphers does not add support for any clients not already supported by the better ECDHE key exchange ciphers, so the presence of those two ciphers is simply not needed.

HTTP Public Key Pinning

I have intentionally removed my previous instructions for how to implement HPKP. Too many servers have become bricked for some users as a result of a configuration mistake, and fucking Google, their browser does not always enforce it, allowing different fingerprints if it looked like a corporate proxy but that is exactly how many MITM attacks look.

Do not use HPKP.

Final Configuration

If implementing everything described above on an IPv4 server using a 2048-bit RSA private key, your virtual host configuration will look something like the following:

#Port 80 Redirect

Redirect permanent /

Redirect permanent /

# Port 443 Redirect

Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"
Redirect permanent /
SSLEngine on
SSLHonorCipherOrder on
SSLCertificateFile            /etc/pki/tls/certs/
SSLCertificateKeyFile       /etc/pki/tls/private/
SSLCACertificateFile      /etc/pki/tls/certs/

#Live Host

Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"
DocumentRoot "/srv/"
SSLEngine on
SSLHonorCipherOrder on
SSLCertificateFile            /etc/pki/tls/certs/
SSLCertificateKeyFile       /etc/pki/tls/private/
SSLCACertificateFile      /etc/pki/tls/certs/
ErrorLog logs/
CustomLog logs/ combined

<Directory "/srv/">
  Options FollowSymlinks
  AllowOverride All
  Require all granted
  php_value include_path "/srv/"

IPv6 Instructions

For IPv6 support, just copy each IPv4 Virtual Host but change the IPv4 address to an IPv6 address.

Note that in Apache, due to a : being used to separate the IP address from the TCP port it listens on, IPv6 addresses must be encapsulated within [] square brackets.

Server Testing

I highly recommend frequently testing the quality of your TLS configuration at Qualys® SSL Labs:

Qualys® SSL Labs SSL Server Test

The server for Naughty.Audio is configured according to the instructions presented here. To see how it performs:

Unsupported Clients

With the above configuration, the following three clients in the Qualys SSL Labs test are not supported:

  1. Android 2.3.7
  2. Internet Explorer 8 / XP
  3. Java 6u45

None of those clients support the TLS SNI extension, severely limiting their usefulness as real world clients. None of those clients still even receives security updates from the vendor. They are dead clients and are not safe for users to be using.

ECDSA Private Keys

The future of x.509 certificates is clearly with ECDSA certificates. Using them is actually very similar to using RSA certificates, the differences are mostly taken care of for you by the software.

At this time, unless I need Tumblr support, I personally only use ECDSA certificates and only support TLS 1.2 (1.3 is not yet final).

For those who want to start playing with ECDSA certificates now, hopefully this section will be of some benefit.

Selection of which Elliptic Curve to use with an ECDSA certificate is the hard part, and it is not very difficult.

To see all the curves available as part of LibreSSL 2.9.2 run the following command:

/usr/bin/libressl ecparam -list_curves

The list is way too long to replicate here. However most of those curves are useless to us.

In the context of an x.509 certificate for a website, we have to use a curve that is supported by the Certificate Authority that will sign the certificate. With Certificate Authorities that support ECDSA certificates, that list is usually fairly short. With Comodo, I believe it is limited to the following curves:

NIST/SECG curve over a 384 bit prime field
NIST/SECG curve over a 521 bit prime field
X9.62/SECG curve over a 256 bit prime field
NIST/SECG/WTLS curve over a 163 bit binary field

That list may change, and other Certificate Authorities may have support for additional curves.

From that list, we can eliminate sect163k1 because 163-bit is below the minimum 256-bit currently considered to be Deployment Best Practices for ECDSA certificates. It also does not appear to be supported by common browsers.

From that list, we can eliminate secp521r1 because several browsers, including current versions of Google Chrome and some versions of Internet Explorer, do not support it.

Between the two remaining options, secp384r1 is probably more difficult to crack simply because the prime field is larger. So that is the option I presently am going with.

I am assuming that prime256v1 is the same curve as secp256r1 that most browsers list as supported, several Internet queries give that impression, but you should double-check before using it.

Curve Paranoia

Not all curves are the same. In cryptography it is very important to know why the parameters for a given curve were chosen.

Without that knowledge, it is possible that the parameters were chosen because they create a weakness to the curve that is not obvious. There just is no way to know.

What we do know, is that in the past the NSA has influenced cryptography for the purpose of making it easier for them to break it in the future. That is why it is very important that decisions like the parameters chosen when developing a specific curve must be transparent for the curve to really be considered secure.

This is not just a hypothetical paranoia, they have done it before.

Both secp384r1 and prime256v1 lack that transparency. It is possible that the parameters chosen in developing those curves were chosen because it was known that certain characteristics might make it possible to crack them in the future. We just do not know.

Private Key Generation

To generate your ECDSA private key using the secp384r1 curve:

pushd /etc/pki/tls/private
umask 0277
/usr/bin/libressl ecparam -name secp384r1 -genkey -out
umask ${DUMASK}

Optional Paranoid Strict TLS 1.2 Support

When I use ECDSA I choose to only support TLS 1.2 clients and use a very limited SSLCipherSuite directive that only includes a small set of ciphers:


Currently that setting results in only three ciphers being available, two that are AEAD and one that is CBC. That is enough to cover most TLS 1.2 capable clients.

It bothers me a little bit that Android 5 and Android 6 are listed as not being supported at Qualys SSL Labs with that configuration. Both were supported with that configuration with LibreSSL 2.5.5.

The failure is because there was a preview version of the ChaCha20 cipher suite and now a final version of the ChaCha20 cipher suite. Those Android clients only support the preview version. LibreSSL 2.5.5 supported both preview and final, but 2.9.2 only supports the finalized version. It is possible that Android will update their clients that support the preview version, and it possible other brothers (e.g. FireFox on Android) are supported, I just do not know.

If you remove the -SHA from the above SSLCipherSuite directive, an additional TLS 1.0 cipher will be available that is supported by those versions of Android.

That is all there is to it. From this point on, you can generate a CSR and proceed to configure Apache the same way you would if using an RSA private key, as discussed in the section Private Key and Certificate Generation.

Live Example

To see how a Paranoid Strict TLS 1.2 LibreLAMP powered server with an ECDSA certificate using a secp384r1 curve measures up: