# Install a TLS certificate on Apache httpd

On Apache 2.4.8 and later, set `SSLCertificateFile` to a file containing your server certificate followed by its intermediates, and `SSLCertificateKeyFile` to the private key. The old `SSLCertificateChainFile` directive is deprecated and logs a warning — Apache reads the chain from the certificate file now, the same way nginx does. On Apache 2.4.7 and earlier the split directives are still required, which is why so much conflicting documentation exists for this.

Source: https://certguru.io/platforms/apache/
Last updated: 2026-08-19
Commands verified against OpenSSL 3.5.7.

---

## Which configuration applies to you

```bash
apache2 -v || httpd -v
```

Expected output:

```
Server version: Apache/2.4.58 (Ubuntu)
```

| Version | Chain goes in |
| --- | --- |
| 2.4.8 and later | `SSLCertificateFile`, concatenated after the leaf |
| 2.4.7 and earlier | A separate `SSLCertificateChainFile` |

## Step 1 — build the chain file

```bash
cat certificate.crt intermediate.crt > /etc/ssl/certs/fullchain.crt
```

## Step 2 — the virtual host

*/etc/apache2/sites-available/example.com-ssl.conf*

```apache

    ServerName  example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/example.com

    SSLEngine on
    SSLCertificateFile    /etc/ssl/certs/fullchain.crt
    SSLCertificateKeyFile /etc/ssl/private/private.key

    SSLProtocol            -all +TLSv1.2 +TLSv1.3
    SSLHonorCipherOrder    off
    SSLSessionTickets      off

    SSLUseStapling on

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

# Stapling cache belongs at server scope, not inside a VirtualHost.
SSLStaplingCache shmcb:/var/run/ocsp(128000)

    ServerName  example.com
    ServerAlias www.example.com
    Redirect permanent / https://example.com/

```

> **SSLStaplingCache must be outside the VirtualHost**
>
> Placing it inside one produces `SSLStaplingCache not configured` at startup, and stapling silently
>   does not happen. It is a server-scope directive.

## Step 3 — enable the modules

```bash
a2enmod ssl headers
a2ensite example.com-ssl
```

## Step 4 — permissions

```bash
chmod 600 /etc/ssl/private/private.key
chown root:root /etc/ssl/private/private.key
```

## Step 5 — test and reload

```bash
apachectl configtest
```

Expected output:

```
Syntax OK
```

```bash
systemctl reload apache2
```

## Step 6 — verify from outside

*Complete chain?*

```bash
openssl s_client -connect example.com:443 -servername example.com -showcerts /dev/null \
  | grep -c "BEGIN CERTIFICATE"
```

Expected output:

```
2
```

*Validates?*

```bash
openssl s_client -connect example.com:443 -servername example.com /dev/null \
  | grep "Verify return code"
```

Expected output:

```
Verify return code: 0 (ok)
```

## Which virtual host is actually serving?

The most useful Apache debugging command there is:

```bash
apachectl -S
```

Expected output:

```
VirtualHost configuration:
*:443     example.com (/etc/apache2/sites-enabled/example.com-ssl.conf:1)
*:80      example.com (/etc/apache2/sites-enabled/example.com.conf:1)
```

If a hostname is not listed, requests for it fall through to the first virtual host on that port —
which is why the wrong certificate gets served.

## Common Apache TLS failures

| Symptom | Cause | Fix |
| --- | --- | --- |
| `Unable to configure RSA server private key` | Key does not match the certificate | [Check the pair](/openssl/check-key-matches-certificate/) |
| `SSLCertificateChainFile is deprecated` | Using the old directive on 2.4.8+ | Concatenate into `SSLCertificateFile` |
| Works in Chrome, fails in curl | Missing intermediate | Build the chain file |
| Apache hangs on start | Encrypted key waiting on a passphrase | [Remove it](/openssl/remove-passphrase-from-key/) |
| Wrong site's certificate served | Virtual host mismatch | Check `apachectl -S` |
| `SSLStaplingCache not configured` | Directive inside a VirtualHost | Move it to server scope |

## Starting from a PFX

```bash
openssl pkcs12 -in certificate.pfx -nocerts -nodes -out /etc/ssl/private/private.key
openssl pkcs12 -in certificate.pfx -clcerts -nokeys -out /tmp/certificate.crt
openssl pkcs12 -in certificate.pfx -cacerts -nokeys -out /tmp/chain.crt
cat /tmp/certificate.crt /tmp/chain.crt > /etc/ssl/certs/fullchain.crt
```


## Frequently asked questions

### Should I still use SSLCertificateChainFile?

Only on Apache 2.4.7 or earlier. From 2.4.8 the directive is deprecated and logs a warning on every start; the chain belongs in SSLCertificateFile instead.

### Where do I check my Apache version?

`apache2 -v` on Debian and Ubuntu, `httpd -v` on RHEL and Fedora. The distinction matters because the correct configuration differs either side of 2.4.8.

### Why does Apache serve the wrong certificate?

Usually because the request reached a different virtual host than you expected. Run `apachectl -S` to see how Apache has resolved the virtual hosts and which one is the default for port 443.

### Can Apache use an encrypted private key?

Yes, but it prompts at startup, which breaks unattended restarts. `SSLPassPhraseDialog exec&#58;/path/to/script` supplies it programmatically if you must keep the passphrase.


## References

- [Apache mod_ssl](https://httpd.apache.org/docs/2.4/mod/mod_ssl.html) — SSLCertificateFile and SSLCertificateChainFile

## Related pages

- https://certguru.io/platforms/nginx/
- https://certguru.io/errors/unable-to-get-local-issuer-certificate/
- https://certguru.io/errors/key-values-mismatch/
- https://certguru.io/concepts/certificate-chain/
- https://certguru.io/convert/pfx-to-pem/
- https://certguru.io/platforms/haproxy/
- https://certguru.io/platforms/tomcat/

--
CertGuru — https://certguru.io
