Install an SSL certificate on Apache

11 min readUpdated

To install an SSL certificate on Apache, load mod_ssl, keep the certificate with its intermediates and the private key in PEM files, and point a virtual host on port 443 at them with SSLEngine on, SSLCertificateFile and SSLCertificateKeyFile [1]. Then run apachectl configtest, apply the change with apachectl graceful [2] and check the result from another machine.

The steps that are the same on every server are on installing a certificate. This page covers what Apache adds to them, including faults that configtest passes and a graceful restart turns into an outage.

Module
mod_ssl; without it the SSL directives are an unknown command [12]
Directives
SSLEngine on, SSLCertificateFile, SSLCertificateKeyFile inside the <VirtualHost> [1]
Chain
in SSLCertificateFile after your certificate from Apache 2.4.8; SSLCertificateChainFile before that [1]
Files read
at startup, as root, and not again until a restart [1]
Test and apply
apachectl configtest, then apachectl graceful [2]

Before you start

  • Your Apache version. apachectl -v prints it [9], and it decides where the chain goes.

  • mod_ssl enabled. On Debian and Ubuntu, sudo a2enmod ssl [5]; on RHEL, dnf install mod_ssl [6]. Without the module, the first SSL directive stops the configuration test [12].

  • The certificate, the intermediates and the private key, in PEM. Both file directives take PEM [1]. A PFX file, for example one exported from Windows, has to become PEM files first: see how to export and import a certificate with its private key.

  • A matching pair. The private key must belong to this certificate. configtest does not compare them (see the testing section below); the comparison is in the key match guide of the SSL tools section.

  • Access to test the configuration and restart the service.

  • The virtual host file that is actually loaded. apachectl -S shows the virtual host settings as Apache parsed them [9]; in our test it listed each virtual host with its file and line, and the default one first [12].

Where the files go and who can read them

The certificate is public data and may be world-readable; the private key file should be readable only by root [1]. That works because Apache reads both at startup, while it still runs as root, before it drops privileges [1].

Where the files go and who can read them
ItemDebian, UbuntuRHEL
Enable the modulea2enmod ssl [5]dnf install mod_ssl [6]
HTTPS site file/etc/apache2/sites-available/default-ssl.conf, enabled with a2ensite default-ssl [5]/etc/httpd/conf.d/ssl.conf, block <VirtualHost _default_:443> [6]
Certificate/etc/ssl/certs [5]/etc/pki/tls/certs [6]
Private key/etc/ssl/private [5]/etc/pki/tls/private [6]
Control commandapache2ctl, also called as apachectl [7]apachectl [6]

Other directories work if SSLCertificateFile and SSLCertificateKeyFile point at them [5]. Restrict the key as Red Hat's guide does [6]:

$ sudo chown root:root /etc/pki/tls/private/example.com.key
$ sudo chmod 600 /etc/pki/tls/private/example.com.key

Warning. If Apache cannot read the key, check the path and the owner rather than widening the mode: a wider mode only exposes the key. Keep it out of the certificate file too; mod_ssl accepts the combination but discourages it [1].

IIS works differently: installing an SSL certificate on IIS means importing the certificate and key into the Windows certificate store, not placing files.

The chain: one file or two

From Apache 2.4.8, SSLCertificateFile can hold your certificate followed by the intermediate certificates, sorted from leaf to root, and this makes SSLCertificateChainFile obsolete [1]. Before 2.4.8, the intermediates went into SSLCertificateChainFile. The combined form is the one nginx reads as well, as covered under how to install an SSL certificate on nginx.

Build the combined file with your certificate first:

$ cat example.com.crt intermediate.crt > example.com.chained.crt
$ openssl crl2pkcs7 -nocrl -certfile example.com.chained.crt | openssl pkcs7 -print_certs -noout | grep subject=
subject=CN = example.com
subject=CN = Example Lab Intermediate

On Apache 2.4.58, a virtual host that still names SSLCertificateChainFile started and sent both certificates in our test [12]. An old configuration therefore keeps working there; for new ones use the combined file, since the documentation marks the separate directive obsolete [1].

Leave the intermediates out, and in the same test openssl s_client stopped at Verify return code: 21 (unable to verify the first certificate) [12]. In browsers the same gap shows up as ERR_CERT_AUTHORITY_INVALID. Why the site still works for whoever installed it, and how a client builds the chain, is covered on the certificate chain page in the Certificates section.

Configure the virtual host

The HTTPS site is its own virtual host on port 443, with encryption switched on in it.

<VirtualHost *:443>
    ServerName example.com
    ServerAlias www.example.com

    SSLEngine on
    SSLCertificateFile    /etc/ssl/certs/example.com.chained.crt
    SSLCertificateKeyFile /etc/ssl/private/example.com.key

    # ... the rest of the site configuration
</VirtualHost>

SSLEngine is off by default for the main server and for every virtual host [1]. Without SSLEngine on, the virtual host above passed configtest, started, and answered plain HTTP on the TLS port; openssl s_client reported wrong version number [12]. Firefox calls the same fault SSL_ERROR_RX_RECORD_TOO_LONG [10].

ServerName and ServerAlias together list every name the site answers for, and Apache matches them against the name in the request [3]. The certificate has to cover each of them, or visitors to the missing name get a name mismatch, ERR_CERT_COMMON_NAME_INVALID in Chrome [11].

Redirect HTTP to HTTPS

A second virtual host on port 80 sends visitors across with a 301 [4]:

<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    Redirect permanent / https://example.com/
</VirtualHost>

The redirect cannot rescue a name the certificate lacks. TLS runs before any HTTP exchange [13], so a visitor who opens https://www.example.com/ sees the certificate before Apache could redirect anything. Both names still belong on the certificate.

Test, apply and what configtest misses

apachectl configtest parses the configuration and prints Syntax OK or the error it found [2]. apachectl graceful runs the same check first, then restarts without aborting open connections [2]. If the check fails, Apache refuses to restart and keeps serving the old configuration [8], and the old certificate with it [12].

$ sudo apachectl configtest
Syntax OK
$ sudo apachectl graceful

The Debian manual warns that the test does not catch all errors [7]. On Apache 2.4.58 we got [12]:

Test, apply and what configtest misses
FaultconfigtestStart or graceful
mod_ssl not loadedInvalid command 'SSLEngine', perhaps misspelled …refused
Wrong path to the certificateSSLCertificateFile: file '…' does not exist or is emptyrefused
Key from another requestSyntax OKAH02565: Certificate and private key … do not match; after graceful, AH00020: Configuration Failed, exiting and the server stopped
Key protected by a pass phrase, built-in dialog, no terminalSyntax OKAH02578: Init: Unable to read pass phrase, AH02312: Fatal error initialising mod_ssl, exiting; the server stopped
No intermediatesSyntax OKstarts; s_client cannot verify
No SSLEngine onSyntax OKstarts; plain HTTP on the TLS port

In both key rows the graceful command returned 0, then the running server exited and the TLS port refused connections. Compare the certificate and the private key before a graceful restart; installing a certificate has the two commands.

Verify from outside

Check from another machine, once for each name in ServerName and ServerAlias: which certificate arrives, whether the chain is complete, and whether the name is covered. An external checker reports all three. With openssl s_client, pass -servername with each name, as in our test [12].

Several sites on one server

Apache first narrows the virtual hosts by address and port, then compares ServerName and ServerAlias with the name the client asked for. If nothing matches, the first virtual host listed for that address and port answers: it is the default virtual host for that pair [3]. Over TLS the client sends that name in the handshake through Server Name Indication, before any HTTP request [13].

In our test, a request for new.example.com, which had no virtual host, received the certificate of example.com, the first one in the file [12]. A new site whose virtual host was never created shows another site's certificate this way.

Each virtual host carries its own SSLCertificateFile and SSLCertificateKeyFile. Several names on one certificate can share one virtual host through ServerAlias [6]. Repeating SSLCertificateFile in one virtual host is for a second key algorithm, such as RSA next to ECC [1].

Common mistakes on Apache

  • mod_ssl is not enabled. configtest reports a syntax error on the first SSL directive, and the message names the directive, not mod_ssl [12].

  • The chain is missing. The server starts, and clients that do not already hold the intermediate fail to verify your certificate.

  • The key belongs to another certificate, or needs a pass phrase. configtest passes, and a graceful restart can take the running server down [12]. With httpd's built-in dialog (SSLPassPhraseDialog builtin) the prompt is on the terminal [1], which a graceful restart cannot answer.

  • The file was changed and Apache was not restarted. The certificate and key files are read only at startup [1], so the old certificate stays in service.

  • A typo in the configuration. apachectl graceful refuses to restart [8], and the old certificate stays in service.

When TLS ends in front of Apache

The certificate visitors see belongs where TLS terminates. If a load balancer or a reverse proxy terminates connections in front of Apache, visitors get the certificate installed there, whatever Apache holds. The installation hub covers that choice.

Automate this with Bridge SSL

Bridge SSL is the certificate layer of Bridge CDN. On Bridge CDN, TLS is terminated at the edge, so there is no certificate file on your machine, no chain to assemble and no service to reload [14]. That certificate is issued and used at the edge, and it is a separate path from the one on this page: the steps above are for a certificate that your own Apache serves. See what the platform offers at Bridge CDN.

FAQ

Which directives does Apache need for SSL?

SSLEngine on, SSLCertificateFile and SSLCertificateKeyFile, in the virtual host that listens on port 443, with mod_ssl loaded. SSLEngine is off by default for the main server and every virtual host, so a host without it serves plain HTTP. [1][12]

Do I need a separate chain file?

Not on Apache 2.4.8 and later: put the intermediates in the SSLCertificateFile file after your certificate. Earlier versions use SSLCertificateChainFile; Apache 2.4.58 still read it in our test, and the documentation calls it obsolete. [1][12]

Apache says the directives are unknown.

mod_ssl is not loaded, so Apache does not know its directives. The test reports it as Invalid command 'SSLEngine', perhaps misspelled or defined by a module not included in the server configuration. Enable the module with a2enmod ssl on Debian and Ubuntu or install mod_ssl on RHEL. [12][5][6]

Should I restart or reload?

Use apachectl graceful: it checks the configuration first and does not abort connections in progress. Compare the certificate and key before you run it, because a key that does not match passes the check and then stops the server during the graceful restart. [2][12]

Why is another site's certificate being served?

No virtual host matched the requested name, so the first virtual host for that address and port answered with its own certificate. Add the name to ServerName or ServerAlias of the right virtual host, and to the certificate. [3][12]

How do I confirm it worked?

From another machine, connect once for each name the virtual host serves and check the certificate presented, the chain and the names it covers. A name configured in Apache but missing from the certificate fails only for visitors.

Sources

  1. Apache HTTP Server 2.4 documentation, mod_ssl: SSLEngine, SSLCertificateFile, SSLCertificateKeyFile, SSLCertificateChainFile.

  2. Apache HTTP Server 2.4 documentation, apachectl: configtest, graceful.

  3. Apache HTTP Server 2.4 documentation, Name-based Virtual Host Support.

  4. Apache HTTP Server 2.4 documentation, mod_alias: Redirect.

  5. Ubuntu Server documentation, How to use Apache2 modules: Configure Apache2 for HTTPS.

  6. Red Hat Enterprise Linux 9, Deploying web servers and reverse proxies: Configuring TLS encryption on an Apache HTTP Server; Loading a DSO module.

  7. Debian manual pages, apache2ctl(8).

  8. Apache HTTP Server 2.4 documentation, Stopping and Restarting Apache HTTP Server.

  9. Apache HTTP Server 2.4 documentation, httpd: options -S, -t, -v.

  10. NSS source, SSLerrs.h: SSL_ERROR_RX_RECORD_TOO_LONG.

  11. Chromium source, net_error_list.h: CERT_COMMON_NAME_INVALID.

  12. Our test: Apache 2.4.58 from the Ubuntu package with mod_ssl, OpenSSL 3.0.13, test certificates for example.com from our own lab root.

  13. Apache HTTP Server 2.4 documentation, SSL/TLS Strong Encryption: FAQ, name-based virtual hosts and Server Name Indication.

  14. Bridge SSL home page, “What happens on my server?”.