How-to guide · Install on a server
How to install a certificate in Apache httpd
In one sentence
Apache httpd serves your certificate with its full chain from one SSLCertificateFile, with a matching key, and you have proved it from outside.
- Platforms
- Linux
- Apache
- Level
- Practitioner · 5 steps
On this page
Before you start
- Your server certificate (the leaf certificate) and the CA’s intermediate certificate or certificates, in PEM format.
- The private key you created with the CSR. It stays on the server.
- Apache httpd 2.4.8 or later (check with
apache2 -vorhttpd -v), root access, and port 443 open. - If you use Let’s Encrypt, certbot can do all of this for you with its Apache plugin.
Why it matters: Apache sends exactly the chain you configure. A missing intermediate often goes unnoticed in desktop browsers but breaks curl, Java, Python and many phones.
Steps
1. Put the full chain in one file
Since Apache 2.4.8, SSLCertificateFile can hold the intermediates too, “sorted from leaf to root”, and this “obsoletes SSLCertificateChainFile” (mod_ssl). Put the leaf first, then the intermediates, and leave the root out (build the full chain):
sudo mkdir -p /etc/ssl/example
cat www.example.com.crt intermediate.crt | sudo tee /etc/ssl/example/fullchain.pem > /dev/null
sudo install -m 600 -o root -g root www.example.com.key /etc/ssl/example/privkey.pem
On RHEL, Fedora and their relatives, use /etc/pki/tls/certs/ and /etc/pki/tls/private/ instead, so the files get the SELinux labels httpd is allowed to read; run sudo restorecon -Rv /etc/pki/tls after copying.
2. Check the key before Apache sees it
openssl x509 -in /etc/ssl/example/fullchain.pem -noout -pubkey | openssl sha256
sudo openssl pkey -in /etc/ssl/example/privkey.pem -pubout | openssl sha256
The two hashes must be identical. This step matters more with Apache than with nginx: in our test, apachectl configtest said Syntax OK with a mismatched key, and the reload then stopped the whole server (step 5 has the log).
3. Enable mod_ssl
On Debian and Ubuntu:
sudo a2enmod ssl
Considering dependency mime for ssl:
Module mime already enabled
Considering dependency socache_shmcb for ssl:
Enabling module socache_shmcb.
Enabling module ssl.
...
Debian’s ports.conf already adds Listen 443 when mod_ssl is loaded. On the RHEL family, install it with sudo dnf install mod_ssl; it adds /etc/httpd/conf.d/ssl.conf.
4. Configure the virtual host
Debian and Ubuntu: /etc/apache2/sites-available/example.com-ssl.conf. RHEL family: a file in /etc/httpd/conf.d/.
<VirtualHost *:443>
ServerName www.example.com
ServerAlias example.com
DocumentRoot /var/www/example
SSLEngine on
SSLCertificateFile /etc/ssl/example/fullchain.pem
SSLCertificateKeyFile /etc/ssl/example/privkey.pem
</VirtualHost>
<VirtualHost *:80>
ServerName www.example.com
ServerAlias example.com
Redirect permanent / https://www.example.com/
</VirtualHost>
Every name in ServerName and ServerAlias must also be in the certificate’s Subject Alternative Name. Leave out SSLCertificateChainFile. For protocol versions, set SSLProtocol -all +TLSv1.2 +TLSv1.3 once in the server-wide SSL configuration (mods-available/ssl.conf or conf.d/ssl.conf). The built-in default is all -SSLv3, which still includes TLS 1.0 and 1.1. More in How to configure TLS securely.
Enable the site on Debian and Ubuntu:
sudo a2ensite example.com-ssl
5. Test and reload
sudo apachectl configtest
Syntax OK
sudo systemctl reload apache2 # RHEL family: sudo systemctl reload httpd
Syntax OK checks the configuration, not whether the key fits the certificate. With a wrong key, our graceful reload logged this (timestamps removed) and Apache exited:
[ssl:emerg] AH02565: Certificate and private key intranet.example.com:443:0 from /etc/ssl/example/fullchain.pem and /etc/ssl/example/privkey.pem do not match
[:emerg] AH00020: Configuration Failed, exiting
So always run step 2 before a reload, and watch sudo tail /var/log/apache2/error.log (RHEL: /var/log/httpd/error_log) afterwards. If Apache did stop, fix the files, repeat step 2 and start it again with sudo systemctl start apache2 (RHEL family: httpd).
Check it worked
From another machine if you can:
openssl s_client -connect www.example.com:443 -servername www.example.com </dev/null 2>/dev/null | grep -E '^ [0-9] s:|^ i:|Verify return code'
Our test server (Apache 2.4.58 on Ubuntu 24.04, private test CA, so we added -CAfile corp-root.crt):
0 s:CN = intranet.example.com
i:C = BE, O = Example Corp, CN = Example Corp Issuing CA 1
1 s:C = BE, O = Example Corp, CN = Example Corp Issuing CA 1
i:C = BE, O = Example Corp, CN = Example Corp Root CA
Verify return code: 0 (ok)
Two certificates, leaf first, code 0. Then curl -sS -o /dev/null -w '%{http_code}\n' https://www.example.com/ should print a status code. You can paste the -showcerts output into the chain checker:
If something goes wrong
- Only one certificate is sent, and clients report
unable to verify the first certificate,unable to get local issuer certificateorPKIX path building failed. With the leaf alone, our test passedconfigtest, started, and servedVerify return code: 21. Rebuild the file (step 1). AH02565: Certificate and private key … do not match. The key belongs to another certificate, or the file starts with the intermediate instead of the leaf; we got this message both ways. Check the order and run step 2.SSLCertificateFile: file '…' does not exist or is empty.configtestcatches this one (AH00526: Syntax error). Fix the path.- The old certificate is still served. Another virtual host matches first. Run
sudo apachectl -Sto list the virtual hosts and which one is the default for port 443. NET::ERR_CERT_COMMON_NAME_INVALID. The certificate’s Subject Alternative Name does not include the name in the address bar, or a different virtual host answered. Check with the hostname matcher.NET::ERR_CERT_DATE_INVALID. The certificate expired, or the client’s clock is wrong.
Why this works
During the TLS handshake, mod_ssl sends the certificates from SSLCertificateFile in file order and signs the handshake with the matching key. The client builds a certificate chain from the leaf, through the intermediates you sent, to a root it already trusts; it cannot use a root you send, and many clients cannot fill in a missing intermediate. See Certificate chains and path validation, The TLS 1.3 handshake and Anatomy of an X.509 certificate.
Go deeper
- mod_ssl reference:
SSLCertificateFile,SSLCertificateKeyFile,SSLProtocoland their defaults. - Apache SSL/TLS how-to: basic and stricter configurations from the Apache project.
- RFC 9846, section 4.5.1: the TLS 1.3 rules for the order of the certificate list.
Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.